
/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import static java.lang.Integer.parseInt;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.List;

@FunctionalInterface
interface VoidAction {
    void get();
}

class Constants {
    public static final int WSL_0XFFFFFFFF = 0xffffffff;
    public static final int WSL_0XFF = 0xff;
    public static final int WSL_0XFE = 0xfe;
    public static final int WSLNEGATIVE_2 = -2;
    public static final int WSLNEGATIVE_3 = -3;
    public static final int WSLNEGATIVE_5 = -5;
    public static final int WSLNEGATIVE_7 = -7;
    public static final int WSLNEGATIVE_32 = -32;
    public static final int WSLNEGATIVE_35 = -35;
    public static final int WSLNEGATIVE_42 = -42;
    public static final int WSLNEGATIVE_111 = -111;
    public static final int WSLNEGATIVE_511 = -511;
    public static final int WSLNEGATIVE_65536 = -65536;
    public static final double WSLDECIMAL_1 = 1.5;
    public static final float WSLDECIMAL_7 = 7.5f;
    public static final int WSL_2 = 2;
    public static final int WSL_3 = 3;
    public static final int WSL_4 = 4;
    public static final int WSL_5 = 5;
    public static final int WSL_6 = 6;
    public static final int WSL_7 = 7;
    public static final int WSL_8 = 8;
    public static final int WSL_9 = 9;
    public static final int WSL_10 = 10;
    public static final int WSL_11 = 11;
    public static final int WSL_12 = 12;
    public static final int WSL_13 = 13;
    public static final int WSL_14 = 14;
    public static final int WSL_15 = 15;
    public static final int WSL_16 = 16;
    public static final int WSL_17 = 17;
    public static final int WSL_18 = 18;
    public static final int WSL_19 = 19;
    public static final int WSL_20 = 20;
    public static final int WSL_21 = 21;
    public static final int WSL_22 = 22;
    public static final int WSL_23 = 23;
    public static final int WSL_24 = 24;
    public static final int WSL_25 = 25;
    public static final int WSL_27 = 27;
    public static final int WSL_29 = 29;
    public static final int WSL_30 = 30;
    public static final int WSL_34 = 34;
    public static final int WSL_35 = 35;
    public static final int WSL_37 = 37;
    public static final int WSL_39 = 39;
    public static final int WSL_40 = 40;
    public static final int WSL_41 = 41;
    public static final int WSL_42 = 42;
    public static final int WSL_43 = 43;
    public static final int WSL_45 = 45;
    public static final int WSL_46 = 46;
    public static final int WSL_47 = 47;
    public static final int WSL_48 = 48;
    public static final int WSL_49 = 49;
    public static final int WSL_52 = 52;
    public static final int WSL_53 = 53;
    public static final int WSL_54 = 54;
    public static final int WSL_55 = 55;
    public static final int WSL_58 = 58;
    public static final int WSL_60 = 60;
    public static final int WSL_62 = 62;
    public static final int WSL_63 = 63;
    public static final int WSL_64 = 64;
    public static final int WSL_65 = 65;
    public static final int WSL_68 = 68;
    public static final int WSL_69 = 69;
    public static final int WSL_72 = 72;
    public static final int WSL_73 = 73;
    public static final int WSL_76 = 76;
    public static final int WSL_79 = 79;
    public static final int WSL_83 = 83;
    public static final int WSL_89 = 89;
    public static final int WSL_91 = 91;
    public static final int WSL_112 = 112;
    public static final int WSL_128 = 128;
    public static final int WSL_211 = 211;
    public static final int WSL_213 = 213;
    public static final int WSL_224 = 224;
    public static final int WSL_228 = 228;
    public static final int WSL_249 = 249;
    public static final int WSL_252 = 252;
    public static final int WSL_254 = 254;
    public static final int WSL_255 = 255;
    public static final int WSL_354 = 354;
    public static final int WSL_385 = 385;
    public static final int WSL_498 = 498;
    public static final int WSL_642 = 642;
    public static final int WSL_645 = 645;
    public static final int WSL_743 = 743;
    public static final int WSL_754 = 754;
    public static final int WSL_767 = 767;
    public static final int WSL_932 = 932;
    public static final int WSL_1234 = 1234;
    public static final int WSL_1575 = 1575;
    public static final int WSL_3478 = 3478;
    public static final int WSL_4657 = 4657;
    public static final int WSL_5565 = 5565;
    public static final int WSL_7624 = 7624;
    public static final int WSL_7804 = 7804;
    public static final int WSL_16383 = 16383;
    public static final int WSL_43696 = 43696;
    public static final int WSL_53899 = 53899;
    public static final int WSL_65493 = 65493;
    public static final int WSL_65535 = 65535;
    public static final int WSL_78453 = 78453;
    public static final int WSL_79201 = 79201;
    public static final int WSL_85732 = 85732;
    public static final int WSL_130991 = 130991;
    public static final int WSL_262140 = 262140;
    public static final int WSL_134217727 = 134217727;
    public static final int WSL_1409286144 = 1409286144;
    public static final int WSL_4294901760 = -65536;
    public static final int WSL_4294967264 = -32;
    public static final int WSL_4294967289 = -7;
    public static final int WSL_4294967294 = -2;
    public static final int WSL_4294967295 = -1;
    public static final int WSL_INT32MAX = 2147483647;
    public static int anonymousVariableCount = 0;
    public static int eBufferCount = 0;
    public static boolean canAllocateEBuffers = true;
    public static LexerToken externalOrigin = new LexerTokenExternal(new Lexer("<external>", "user", 0, ""), 0, "", "");
    public static String breakException = "BreakException";
    public static String continueException = "ContinueException";
    public static List<String> addressSpaces = Arrays.asList("constant", "device", "threadgroup", "thread");
    public static EPtr assignmentEptr;
    public static String regExpLastMatch = "";
    public static HmSymbol anything = new HmSymbol();
    public static Lexer lexer = null;
    public static String hmOrigin = "";
    public static int nub = 0;
}

class Base {
    public Map<String, Function<Base, Object>> mapReturn() {
        return new HashMap<>();
    }

    public Function<Base, Object> temporaryFn = null;
}

class Node extends Base {
    public boolean isBecome = false;
    public Object myCall(Function<Base, Object> method, Base thisArg, Base node) {
        thisArg.temporaryFn = method;
        Object result = thisArg.temporaryFn.apply(node);
        thisArg.temporaryFn = null;
        return result;
    }

    public Object visit(Base visitor) {
        String className = this.getClass().getSimpleName();
        if (this.isBecome) {
            className = "IdentityExpression";
        }
        if (Arrays.asList("IntLiteral", "UintLiteral", "DoubleLiteral", "FloatLiteral").contains(className)) {
            className = "GenericLiteral";
        }
        if ( Arrays.asList("IntLiteralType", "UintLiteralType", "DoubleLiteralType", "FloatLiteralType").contains(className)) {
            className = "GenericLiteralType";
        }
        String methodName = "visit" + className;
        Function<Base, Object> visitFunc = (Function<Base, Object>) visitor.mapReturn().get(methodName);
        if (visitFunc == null) {
            throw new Error("No visit function for " + this.getClass().getSimpleName() + " in " + visitor.getClass().getSimpleName());
        }
        Object returnValue = myCall(visitFunc, visitor, this);
        return returnValue;
    }

    public static Node visit(Node node, Base visitor) {
        if (node != null) {
            return (Node) node.visit(visitor);
        }
        return node;
    }

    public boolean unify(UnificationContext unificationContext, Node other) {
        if (other == null) {
            throw new Error("Null other");
        }
        Node unifyThis = this.unifyNode();
        Node unifyOther = other.unifyNode();
        if (unifyThis == unifyOther) {
            return true;
        }
        if (unifyOther.typeVariableUnify(unificationContext, unifyThis)) {
            return true;
        }
        return unifyThis.unifyImpl(unificationContext, unifyOther);
    }

    public boolean unifyImpl(UnificationContext unificationContext, Node other) {
        if (other.typeVariableUnify(unificationContext, this)) {
            return true;
        }
        return this == other;
    }

    public boolean typeVariableUnify(UnificationContext unificationContext, Node other) {
        return false;
    }

    public boolean typeVariableUnifyImpl(UnificationContext unificationContext, Node other) {
        Node realThis = unificationContext.find(this);
        if (realThis != this) {
            return realThis.unify(unificationContext, other);
        }
        unificationContext.union(this, other);
        return true;
    }
    // Most type variables don't care about this.
    public Supplier<VerifyResult> prepareToVerify(UnificationContext unificationContext) {
        return null;
    }

    public void commitUnification(UnificationContext unificationContext) {}

    public Node unifyNode() {
        if (isBecome) {
            return this.target().unifyNode();
        } else {
            return this;
        }
    }

    public boolean isUnifiable() {
        return false;
    }

    public boolean isLiteral() {
        return false;
    }

    public boolean isNative() {
        return false;
    }

    public int conversionCost(UnificationContext unificationContext) {
        return 0;
    }

    public UnificationContext equals(Node other) {
        UnificationContext unificationContext = new UnificationContext(new ArrayList<Node>());
        if (this.unify(unificationContext, other) && unificationContext.verify().result) {
            return unificationContext;
        }
        return null;
    }

    public UnificationContext equalsWithCommit(Node other) {
        UnificationContext unificationContext = this.equals(other);
        if (unificationContext == null) {
            return null;
        }
        unificationContext.commit();
        return unificationContext;
    }

    public TypeRef commit() {
        UnificationContext unificationContext = new UnificationContext(new ArrayList<Node>());
        unificationContext.addExtraNode(this);
        VerifyResult result = unificationContext.verify();
        if (!result.result) {
            throw new WTypeError("node.origin.originString", "Could not infer type: " + result.reason);
        }
        unificationContext.commit();
        return (TypeRef) unificationContext.find(this);
    }

    public HmType substitute(List<Node> parameters, List<Node> argumentList) {
        return (HmType) this.visit(new Substitution(parameters, argumentList));
    }

    public HmType substituteToUnification(List<Node> parameters, UnificationContext unificationContext) {
        return this.substitute(
                parameters,
                parameters.stream().map((Node paramter) -> unificationContext.find(paramter)).collect(Collectors.toList()));
    }

    public LexerToken origin = null;
    public String _name = "";

    public String name() {
        return this._name;
    }

    public void name(String newValue) {
        this._name = newValue;
    }

    public boolean isPrimitive() {
        return false;
    }

    public Node _struct1 = null;

    public Node struct1() {
        return this._struct1;
    }

    public void setStruct1(Node newValue) {
        this._struct1 = newValue;
    }

    public Node _body = null;

    public Node body() {
        return this._body;
    }

    public void body(Node newValue) {
        this._body = newValue;
    }

    public Expression _initialization = null;

    public Expression initialization() {
        return this._initialization;
    }

    public void initialization(Expression newValue) {
        this._initialization = newValue;
    }

    public HmValue _condition = null;

    public HmValue condition() {
        return this._condition;
    }

    public void condition(HmValue newValue) {
        this._condition = newValue;
    }

    public Expression _increment = null;

    public Expression increment() {
        return this._increment;
    }

    public void increment(Expression newValue) {
        this._increment = newValue;
    }

    public HmValue _target = null;

    public HmValue target() {
        return this._target;
    }

    public void target(HmValue newValue) {
        this._target = newValue;
    }

    public HmType _hmType = null;

    public HmType hmType() {
        return this._hmType;
    }

    public void hmType(HmType newValue) {
        this._hmType = newValue;
    }

    public Program program = null;
    public EPtr returnEPtr = null;
    public EPtr resultEPtr = null;
    public EPtr _ePtr = null;

    public EPtr ePtr() {
        return this._ePtr;
    }

    public void ePtr(EPtr newValue) {
        this._ePtr = newValue;
    }

    public String _kind = "";

    public void setKind(String newValue) {
        this._kind = newValue;
    }

    public String kind() {
        return this._kind;
    }


    public VerifyResult verifyAsParameter(UnificationContext unificationContext) {
        return new VerifyResult(false);
    }

    public VerifyResult verifyAsArgument(UnificationContext unificationContext) {
        return new VerifyResult(false);
    }

    public String toString() {
        if (isBecome) {
            return "(" + this.target().toString() + ")";
        } else {
            return "Node";
        }
    }
    //重写方法
    public Node visitNativeType(Node node) {
        return node;
    }

    @Override
    public Map<String, Function<Base, Object>> mapReturn() {
        return super.mapReturn();
    }
}

class HmValue extends Node {
    HmValue() {
        super();
    }

    public String _addressSpace = null;

    public String addressSpace() {
        if (this.isBecome) {
            return this.target()._addressSpace;
        } else {
            return this._addressSpace;
        }
    }

    public void addressSpace(String addressSpace) {
        this._addressSpace = addressSpace;
    }

    @Override
    public String kind() {
        return "Value";
    }

    @Override
    public void setKind(String newValue) {
        super.setKind(newValue);
    }

    public boolean isConstexpr() {
        if (this.isBecome) {
            return this.target().isConstexpr();
        } else {
            return false;
        }
    }

    public boolean isLValue() {
        if (this.isBecome) {
            return this.target().isLValue();
        } else {
            return false;
        }
    }

    public String notLValueReason() {
        return null;
    }

    public String notLValueReasonString() {
        String result = this.notLValueReason();
        if (result != null) {
            return "\n" + result;
        }
        return "";
    }

    public void become(Expression otherValue) {
        LexerToken origin = this.origin;
        this.isBecome = true;
        this.origin = origin;
        this.target(otherValue);
    }
}

class HmType extends Node {
    public List<Node> typeParameters = new ArrayList<>();
    public int _size = -1;
    public Function<Double, Boolean> canRepresent = new Function<Double, Boolean>() {
        @Override
        public Boolean apply(Double integer) {
            return false;
        }
    };

    @Override
    public String kind() {
        return "Type";
    }

    @Override
    public void setKind(String newValue) {
    }

    public boolean isPtr() {
        return false;
    }

    public boolean isArray() {
        return false;
    }

    public boolean isArrayRef() {
        return false;
    }

    public boolean isRef() {
        return this.isPtr() || this.isArrayRef();
    }

    public boolean isNumber() {
        return false;
    }

    public boolean isInt() {
        return false;
    }

    public boolean isFloating() {
        return false;
    }

    public ProtocolRef protocol1() {
        return null;
    }

    public VerifyResult inherits(ProtocolRef protocol1) {
        if (protocol1 == null) {
            return new VerifyResult(true);
        }
        return protocol1.hasHeir(this);
    }

    public HmType instantiatedType() {
        return (HmType) this.visit(new InstantiateImmediates());
    }

    public HmValue argumentForAndOverload(LexerToken origin, HmValue value) {
        return new MakePtrExpression(origin, value);
    }

    public ReferenceType argumentTypeForAndOverload(LexerToken origin) {
        return new PtrType(origin, "thread", this);
    }

    public HmType returnTypeFromAndOverload(LexerToken origin) {
        throw new WTypeError("Type error at " + origin.originString(), " By-pointer overload returned non-pointer type: " + this.toString());
    }

    public int size() {
        return this._size;
    }

    public void size(int value) {
        this._size = value;
    }

    public HmType elementType() {
        return null;
    }

    public Expression numElements() {
        return null;
    }

    public String addressSpace() {
        return "";
    }


    public List<Node> typeArguments = new ArrayList<>();

    public TypeRef populateDefaultValue(EBuffer buffer, int offset) {
        return null;
    }

    public List<ResultMemberInfo> allValues() {
        List<ResultMemberInfo> re = new ArrayList<ResultMemberInfo>();
        for (int i = 0; i <=  Constants.WSL_0XFF; i++) {
            re.add(new ResultMemberInfo(i, String.valueOf(i)));
        }
        return re;
    }
}

class ReferenceType extends HmType {
    private String _addressSpace;
    private HmType _elementType;

    ReferenceType(LexerToken origin, String addressSpace, HmType elementType) {
        super();
        if (elementType == null) {
            throw new Error("Null elementType");
        }
        if (origin == null) {
            throw new Error("Null origin");
        }
        AllFunction.validateAddressSpace(addressSpace);
        this.origin = origin;
        this._addressSpace = addressSpace;
        this._elementType = elementType;
    }

    @Override
    public String addressSpace() {
        return this._addressSpace;
    }

    @Override
    public int size() {
        return 1;
    }

    @Override
    public void size(int value) {

    }

    public HmType elementType() {
        return this._elementType;
    }

    @Override
    public TypeRef populateDefaultValue(EBuffer buffer, int offset) {
        buffer.eBufferSet(offset, null);
        return null;
    }
}

class Expression extends HmValue {
    Expression(LexerToken origin) {
        super();
        this.origin = origin;
    }
    public Object _value;
    public Object value() {
        return this._value;
    }

    public void value(Object newValue) {
        this._value = newValue;
    }
    public HmValue _lValue = null;

    public HmValue lValue() {
        return _lValue;
    }

    public void lValue(HmValue lValue) {
        this._lValue = lValue;
    }

    public double valueForSelectedType() {
        return 0.0;
    }
}

class Rewriter extends Base {
    private Map<HmValue, HmValue> mappingPrivate = new HashMap<HmValue, HmValue>();

    Rewriter() {
        super();
        this.mappingPrivate = new HashMap<HmValue, HmValue>();
    }

    public HmValue mapNode(HmValue oldItem, HmValue newItem) {
        this.mappingPrivate.put(oldItem, newItem);
        return newItem;
    }

    public HmValue getMapping(HmValue oldItem) {
        if (oldItem == null) {
            return oldItem;
        }
        HmValue result = this.mappingPrivate.get(oldItem);
        if (result != null) {
            return result;
        }
        return oldItem;
    }

    // We return identity for anything that is not inside a function/struct body. When processing
    // function bodies, we only recurse into them and never out of them - for example if there is a
    // function call to another function then we don't rewrite the other function. This is how we stop
    // that.

    public FuncDef visitFuncDef(Node node) {
        return (FuncDef) node;
    }

    public NativeFunc visitNativeFunc(Node node) {
        return (NativeFunc) node;
    }

    public NativeFuncInstance visitNativeFuncInstance(Node node) {
        return (NativeFuncInstance) node;
    }

    public NativeType visitNativeType(Node node) {
        return (NativeType) node;
    }

    public TypeDef visitTypeDef(Node node) {
        return (TypeDef) node;
    }

    public StructType visitStructType(Node node) {
        return (StructType) node;
    }

    public ConstexprTypeParameter visitConstexprTypeParameter(Node node) {
        return (ConstexprTypeParameter) node;
    }

    public ProtocolDecl visitProtocolDecl(Node node) {
        return (ProtocolDecl) node;
    }

    public EnumType visitEnumType(Node node) {
        return (EnumType) node;
    }

    // This is almost wrong. We instantiate Func in Substitution in ProtocolDecl. Then, we end up
    // not rewriting type variables. I think that just works because not rewriting them there is OK.
    // Everywhere else, it's mandatory that we don't rewrite these because we always assume that
    // type variables are outside the scope of rewriting.

    public TypeVariable visitTypeVariable(Node node) {
        return (TypeVariable) node;
    }

    public ProtocolFuncDecl visitProtocolFuncDecl(Node node) {
        ProtocolFuncDecl newNode = (ProtocolFuncDecl) node;
        ProtocolFuncDecl result = new ProtocolFuncDecl(
                newNode.origin,
                newNode.name(),
                (HmType) newNode.returnType().visit(this),
                newNode.typeParameters().stream().map(parameter -> (Node) parameter.visit(this)).collect(Collectors.toList()),
                newNode.parameters().stream().map(parameter -> (FuncParameter) parameter.visit(this)).collect(Collectors.toList()),
                newNode.isCast(),
                newNode.shaderType());
        result.protocolDecl = newNode.protocolDecl;
        result.possibleOverloads = newNode.possibleOverloads;
        return result;
    }

    public NativeTypeInstance visitNativeTypeInstance(Node node) {
        NativeTypeInstance newNode = (NativeTypeInstance) node;
        return new NativeTypeInstance(
                (Node) newNode.hmType().visit(this),
                newNode.typeArguments.stream().map(argument -> (TypeVariable) argument.visit(this)).collect(Collectors.toList())
        );
    }

    public FuncParameter visitFuncParameter(Node node) {
        FuncParameter result = new FuncParameter(node.origin, node.name(), (HmType) node.hmType().visit(this));
        this.mapNode((HmValue) node, result);
        result.ePtr(node.ePtr());
        return result;
    }

    public VariableDecl visitVariableDecl(Node node) {
        VariableDecl result = new VariableDecl(
                ((VariableDecl) node).origin,
                node.name(),
                (Node) node.hmType().visit(this),
                (HmValue) Node.visit(((VariableDecl) node).initializer(), this));
        this.mapNode(((HmValue) node), result);
        result.ePtr(node.ePtr());
        return result;
    }

    public Block visitBlock(Node node) {
        Block result = new Block(((Block) node).originStr());
        for (Node statement : ((Block) node).statements()) {
            result.add((Node) statement.visit(this));
        }
        return result;
    }

    public CommaExpression visitCommaExpression(Node node) {
        return new CommaExpression(
                node.origin,
                ((CommaExpression) node).list().stream().map(expression -> {
            HmValue result = (HmValue) expression.visit(this);
            if (result == null) {
                throw new Error("Null result from " + expression.toString());
            }
            return result;
        }).collect(Collectors.toList()));
    }

    public ProtocolRef visitProtocolRef(Node node) {
        return (ProtocolRef) node;
    }

    public TypeRef visitTypeRef(Node node) {
        TypeRef result = new TypeRef(
                node.origin,
                node.name(),
                ((TypeRef) node).typeArguments.stream().map(args -> (Node) args.visit(this)).collect(Collectors.toList()));
        result.hmType((HmType) Node.visit(node.hmType(), this));
        return result;
    }

    public Field visitField(Node node) {
        return new Field(node.origin, node.name(), (HmType) node.hmType().visit(this));
    }

    public EnumMember visitEnumMember(EnumMember node) {
        return new EnumMember(node.origin, node.name(), (Expression) node.value.visit(this));
    }

    public EnumLiteral visitEnumLiteral(Node node) {
        EnumLiteral newNode = (EnumLiteral) node;
        EnumLiteral result = new EnumLiteral(newNode.origin, newNode.member());
        result.ePtr(newNode.ePtr());
        return result;
    }

    public ReferenceType visitReferenceType(ReferenceType node) {
        if (node instanceof PtrType) {
            return new PtrType(node.origin, node.addressSpace(), (HmType) node.elementType().visit(this));
        }
        return new ArrayRefType(node.origin, node.addressSpace(), (HmType) node.elementType().visit(this));
    }

    public PtrType visitPtrType(Node node) {
        return (PtrType) this.visitReferenceType((PtrType) node);
    }

    public ArrayRefType visitArrayRefType(Node node) {
        return (ArrayRefType) this.visitReferenceType((ArrayRefType) node);
    }

    public ArrayType visitArrayType(Node node) {
        return new ArrayType(node.origin, (HmType) ((ArrayType) node).elementType().visit(this), (Expression) ((ArrayType) node).numElements().visit(this));
    }

    public Assignment visitAssignment(Node node) {
        Assignment result = new Assignment(node.origin, (HmValue) ((Assignment) node).lhs().visit(this), (HmValue) ((Assignment) node).rhs().visit(this), null);
        result.hmType((HmType) Node.visit(node.hmType(), this));
        return result;
    }

    public ReadModifyWriteExpression visitReadModifyWriteExpression(Node node) {
        ReadModifyWriteExpression newNode = (ReadModifyWriteExpression) node;
        ReadModifyWriteExpression result = new ReadModifyWriteExpression(newNode.origin, (Expression) newNode.lValue().visit(this), null);
        result.oldValueVar = (AnonymousVariable) newNode.oldValueVar.visit(this);
        result.newValueVar = (AnonymousVariable) newNode.newValueVar.visit(this);
        result.newValueExp = (Expression) newNode.newValueExp.visit(this);
        result.resultExp = (VariableRef) newNode.resultExp.visit(this);
        return result;
    }

    public DereferenceExpression visitDereferenceExpression(Node node) {
        DereferenceExpression result = new DereferenceExpression(node.origin, (HmValue) ((DereferenceExpression) node).ptr().visit(this), null, null);
        result.hmType((HmType) Node.visit(node.hmType(), this));
        result.addressSpace(((DereferenceExpression) node).addressSpace());
        return result;
    }

    public void handlePropertyAccessExpression(PropertyAccessExpression result, PropertyAccessExpression node) {
        result.possibleGetOverloads = node.possibleGetOverloads;
        result.possibleSetOverloads = node.possibleSetOverloads;
        result.possibleAndOverloads = node.possibleAndOverloads;
        result.baseType = (HmType) Node.visit(node.baseType, this);
        result.callForGet = (CallExpression) Node.visit(node.callForGet, this);
        result.resultTypeForGet = (HmType) Node.visit(node.resultTypeForGet, this);
        result.callForAnd = (CallExpression) Node.visit(node.callForAnd, this);
        result.resultTypeForAnd = (HmType) Node.visit(node.resultTypeForAnd, this);
        result.callForSet = (CallExpression) Node.visit(node.callForSet, this);
        result.errorForSet = node.errorForSet;
        result.updateCalls();
    }

    public DotExpression visitDotExpression(Node node) {
        DotExpression result = new DotExpression(
                ((DotExpression) node).origin,
                (Expression) (((DotExpression) node).struct1()).visit(this),
                ((DotExpression) node).fieldName);
        this.handlePropertyAccessExpression(result, ((PropertyAccessExpression) node));
        return result;
    }


    public IndexExpression visitIndexExpression(Node node) {
        IndexExpression newNode = (IndexExpression) node;
        IndexExpression result = new IndexExpression(newNode.origin, (Expression) newNode.array().visit(this), (Expression) newNode.index.visit(this));
        this.handlePropertyAccessExpression(result, newNode);
        return result;
    }

    public MakePtrExpression visitMakePtrExpression(Node node) {
        MakePtrExpression result = new MakePtrExpression(((MakePtrExpression) node).origin, (Expression) ((MakePtrExpression) node).lValue().visit(this));
        result.ePtr(node.ePtr());
        return result;
    }

    public MakeArrayRefExpression visitMakeArrayRefExpression(Node node) {
        MakeArrayRefExpression result = new MakeArrayRefExpression(
                ((MakeArrayRefExpression) node).origin,
                (HmValue) ((MakeArrayRefExpression) node).lValue().visit(this));
        if (((MakeArrayRefExpression) node).numElements != null) {
            result.numElements = (Expression) ((MakeArrayRefExpression) node).numElements.visit(this);
        }
        result.ePtr(((MakeArrayRefExpression) node).ePtr());
        return result;
    }

    public ConvertPtrToArrayRefExpression visitConvertPtrToArrayRefExpression(Node node) {
        ConvertPtrToArrayRefExpression newNode = (ConvertPtrToArrayRefExpression) node;
        ConvertPtrToArrayRefExpression result = new ConvertPtrToArrayRefExpression(newNode.origin, (VariableRef) newNode.lValue().visit(this));
        result.ePtr(node.ePtr());
        return result;
    }

    public VariableRef visitVariableRef(Node node) {
        VariableRef result = new VariableRef(((VariableRef) node).origin, node.name());
        result.variable = this.getMapping(((VariableRef) node).variable);
        return result;
    }

    public Return visitReturn(Node node) {
        return new Return(node.origin,  (HmValue)Node.visit(((Return) node).value(), this));
    }

    public Continue visitContinue(Node node) {
        Continue newNode = (Continue) node;
        return new Continue(newNode.origin);
    }

    public Break visitBreak(Node node) {
        Break newNode = (Break) node;
        return new Break(newNode.origin);
    }

    public TrapStatement visitTrapStatement(Node node) {
        return new TrapStatement(node.origin);
    }

    public GenericLiteral visitGenericLiteral(Node node) {
        // FIXME: This doesn't seem right.
        GenericLiteral newNode = (GenericLiteral) node;
        GenericLiteral result = new GenericLiteral(newNode.origin, (Double) newNode.value(), null);
        result.hmType((HmType) newNode.hmType().visit(this));
        result.ePtr(node.ePtr());
        return result;
    }

    public GenericLiteralType visitGenericLiteralType(Node node) {
        GenericLiteralType newNode = (GenericLiteralType) node;
        GenericLiteralType result = new GenericLiteralType(newNode.origin, newNode.value(), newNode._config);
        result.hmType((HmType) Node.visit(newNode.hmType(), this));
        result.preferredType = (TypeRef) newNode.preferredType.visit(this);
        return result;
    }

    public BoolLiteral visitBoolLiteral(Node node) {
        return (BoolLiteral) node;
    }

    public NullLiteral visitNullLiteral(Node node) {
        NullLiteral newNode = (NullLiteral) node;
        NullLiteral result = new NullLiteral(newNode.origin);
        result.hmType((HmType) newNode.hmType().visit(this));
        result.ePtr(newNode.ePtr());
        return result;
    }

    public NullType visitNullType(Node node) {
        NullType newNode = (NullType) node;
        NullType result = new NullType(newNode.origin);
        result.hmType((HmType) Node.visit(newNode.hmType(), this));
        return result;
    }

    public CallExpression processDerivedCallData(CallExpression node, CallExpression result) {
        Function<List<Node>, List<Node>> handleTypeArguments = new Function<List<Node>, List<Node>>() {
            @Override
            public List<Node> apply(List<Node> actualTypeArguments) {
                if (actualTypeArguments != null) {
                    return actualTypeArguments.stream().map((Node actualTypeArgument) -> (Node) actualTypeArgument.visit(Rewriter.this)).collect(Collectors.toList());
                } else {
                    return null;
                }
            }
        };

        result.actualTypeArguments = handleTypeArguments.apply(node.actualTypeArguments);
        result.instantiatedActualTypeArguments = handleTypeArguments.apply(node.instantiatedActualTypeArguments);
        List<HmType> argumentTypes = node.argumentTypes;
        if (argumentTypes.size() > 0) {
            result.argumentTypes = argumentTypes.stream().map((HmType argumentType) -> (HmType) argumentType.visit(this)).collect(Collectors.toList());
        }
        result.func1 = node.func1;
        result.nativeFuncInstance = node.nativeFuncInstance;
        result.possibleOverloads = node.possibleOverloads;
        if (node.isCast()) {
            TypeRef newValue = (TypeRef) node.returnType().visit(this);
            result.setCastData(newValue);
        }
        result.resultType = Node.visit(node.resultType, this);
        result.resultEPtr = node.resultEPtr;
        return result;
    }

    public CallExpression visitCallExpression(Node node) {
        CallExpression newNode = (CallExpression) node;
        CallExpression result = new CallExpression(
                newNode.origin,
                node.name(),
                newNode.typeArguments().stream().map(typeArgument -> (Node) typeArgument.visit(this)).collect(Collectors.toList()),
                newNode.argumentList().stream().map(argument -> (HmValue) Node.visit(argument, this)).collect(Collectors.toList())
        );
        return this.processDerivedCallData(newNode, result);
    }

    public FunctionLikeBlock visitFunctionLikeBlock(Node node) {
        FunctionLikeBlock result = new FunctionLikeBlock(
                ((FunctionLikeBlock) node).origin,
                (HmType) Node.visit(((FunctionLikeBlock) node).returnType(), this),
                ((FunctionLikeBlock) node).argumentList().stream().map(argument -> (Node) argument.visit(this)).collect(Collectors.toList()),
                ((FunctionLikeBlock) node).parameters().stream().map(parameter -> (FuncParameter) parameter.visit(this)).collect(Collectors.toList()),
                (Block) ((FunctionLikeBlock) node).body().visit(this));
        result.returnEPtr = node.returnEPtr;
        return result;
    }

    public LogicalNot visitLogicalNot(Node node) {
        LogicalNot newNode = (LogicalNot) node;
        LogicalNot result = new LogicalNot(newNode.origin, (HmValue) newNode.operand().visit(this));
        result.ePtr(node.ePtr());
        return result;
    }

    public LogicalExpression visitLogicalExpression(Node node) {
        LogicalExpression newNode = (LogicalExpression) node;
        LogicalExpression result = new LogicalExpression(
                newNode.origin,
                newNode.text(),
                (HmValue) newNode.left().visit(this),
                (HmValue) newNode.right().visit(this));
        result.ePtr(newNode.ePtr());
        return result;
    }

    public IfStatement visitIfStatement(Node node) {
        IfStatement newNode = (IfStatement) node;
        return new IfStatement(newNode.origin, (HmValue) newNode.conditional().visit(this), (Node) newNode.body().visit(this), Node.visit(newNode.elseBody(), this));
    }

    public WhileLoop visitWhileLoop(Node node) {
        WhileLoop newNode = (WhileLoop) node;
        return new WhileLoop(newNode.origin, (HmValue) newNode.conditional().visit(this), (Node) newNode.body().visit(this));
    }

    public DoWhileLoop visitDoWhileLoop(Node node) {
        DoWhileLoop newNode = (DoWhileLoop) node;
        return new DoWhileLoop(newNode.origin, (Block) newNode.body().visit(this), (HmValue) newNode.conditional().visit(this));
    }

    public ForLoop visitForLoop(Node node) {
        ForLoop newNode = (ForLoop) node;
        return new ForLoop(
                newNode.origin,
                (Expression) Node.visit(newNode.initialization(), this),
                (HmValue) Node.visit(newNode.condition(), this),
                (Expression) Node.visit(newNode.increment(), this),
                (Node) newNode.body().visit(this));
    }

    public SwitchStatement visitSwitchStatement(Node node) {
        SwitchStatement newNode = (SwitchStatement) node;
        SwitchStatement result = new SwitchStatement(newNode.origin, (HmValue) Node.visit(newNode.value(), this));
        for (SwitchCase switchCase : newNode.switchCases()) {
            result.add((SwitchCase) switchCase.visit(this));
        }
        result.hmType((HmType) Node.visit(newNode.hmType(), this));
        return result;
    }

    public SwitchCase visitSwitchCase(Node node) {
        SwitchCase newNode = (SwitchCase) node;
        return new SwitchCase(newNode.origin, (Expression) Node.visit(newNode.value(), this), (Block) newNode.body().visit(this));
    }

    public AnonymousVariable visitAnonymousVariable(Node node) {
        AnonymousVariable newNode = (AnonymousVariable) node;
        AnonymousVariable result = new AnonymousVariable(newNode.origin, (HmType) newNode.hmType().visit(this));
        result._index = newNode._index;
        this.mapNode((HmValue) newNode, result);
        result.ePtr(newNode.ePtr());
        return result;
    }

    public IdentityExpression visitIdentityExpression(Node node) {
        return new IdentityExpression((HmValue) node.target().visit(this));
    }

    public TypeOrVariableRef visitTypeOrVariableRef(Node node) {
        return new TypeOrVariableRef(node.origin, node.name());
    }

    @Override
    public Map<String, Function<Base, Object>> mapReturn() {
        Map<String, Function<Base, Object>> methods = super.mapReturn();
        methods.put("visitNativeType", base -> visitNativeType((Node) base));
        methods.put("visitTypeDef", base -> visitTypeDef((Node) base));
        methods.put("visitTypeRef", base -> visitTypeRef((Node) base));
        methods.put("visitNativeFunc", base -> visitNativeFunc((Node) base));

        methods.put("visitFuncParameter", base -> visitFuncParameter((Node) base));
        methods.put("visitFuncDef", base -> visitFuncDef((Node) base));
        methods.put("visitBlock", base -> visitBlock((Node) base));
        methods.put("visitReturn", base -> visitReturn((Node) base));

        methods.put("visitCallExpression", base -> visitCallExpression((Node) base));
        methods.put("visitVariableRef", base -> visitVariableRef((Node) base));
        methods.put("visitGenericLiteral", base -> visitGenericLiteral((Node) base));
        methods.put("visitGenericLiteralType", base -> visitGenericLiteralType((Node) base));

        methods.put("visitIfStatement", base -> visitIfStatement((Node) base));
        methods.put("visitBoolLiteral", base -> visitBoolLiteral((Node) base));
        methods.put("visitLogicalNot", base -> visitLogicalNot((Node) base));
        methods.put("visitProtocolDecl", base -> visitProtocolDecl((Node) base));

        methods.put("visitProtocolFuncDecl", base -> visitProtocolFuncDecl((Node) base));
        methods.put("visitTypeVariable", base -> visitTypeVariable((Node) base));
        methods.put("visitCommaExpression", base -> visitCommaExpression((Node) base));
        methods.put("visitVariableDecl", base -> visitVariableDecl((Node) base));
        methods.put("visitProtocolRef", base -> visitProtocolRef((Node) base));
        methods.put("visitStructType", base -> visitStructType((Node) base));
        methods.put("visitField", base -> visitField((Node) base));
        methods.put("visitTypeOrVariableRef", base -> visitTypeOrVariableRef((Node) base));

        methods.put("visitAssignment", base -> visitAssignment((Node) base));
        methods.put("visitDotExpression", base -> visitDotExpression((Node) base));
        methods.put("visitLogicalExpression", base -> visitLogicalExpression((Node) base));
        methods.put("visitPtrType", base -> visitPtrType((Node) base));

        methods.put("visitMakePtrExpression", base -> visitMakePtrExpression((Node) base));
        methods.put("visitDereferenceExpression", base -> visitDereferenceExpression((Node) base));
        methods.put("visitTrapStatement", base -> visitTrapStatement((Node) base));
        methods.put("visitArrayRefType", base -> visitArrayRefType((Node) base));

        methods.put("visitConstexprTypeParameter", base -> visitConstexprTypeParameter((Node) base));
        methods.put("visitArrayType", base -> visitArrayType((Node) base));
        methods.put("visitIdentityExpression", base -> visitIdentityExpression((Node) base));

        methods.put("visitAnonymousVariable", base -> visitAnonymousVariable((Node) base));
        methods.put("visitEnumType", base -> visitEnumType((Node) base));
        methods.put("visitNullLiteral", base -> visitNullLiteral((Node) base));

        methods.put("visitNullType", base -> visitNullType((Node) base));
        methods.put("visitMakeArrayRefExpression", base -> visitMakeArrayRefExpression((Node) base));
        methods.put("visitIndexExpression", base -> visitIndexExpression((Node) base));
        methods.put("visitSwitchStatement", base -> visitSwitchStatement((Node) base));

        methods.put("visitSwitchCase", base -> visitSwitchCase((Node) base));
        methods.put("visitBreak", base -> visitBreak((Node) base));
        methods.put("visitEnumLiteral", base -> visitEnumLiteral((Node) base));
        methods.put("visitForLoop", base -> visitForLoop((Node) base));

        methods.put("visitConvertPtrToArrayRefExpression", base -> visitConvertPtrToArrayRefExpression((Node) base));
        methods.put("visitDoWhileLoop", base -> visitDoWhileLoop((Node) base));
        methods.put("visitWhileLoop", base -> visitWhileLoop((Node) base));
        methods.put("visitContinue", base -> visitContinue((Node) base));

        return methods;
    }
}

class Visitor extends Base {
    public void visitProgram(Node node) {
        for (Node statement : ((Program) node).topLevelStatements()) {
            statement.visit(this);
        }
    }

    public void visitFunc(Node node) {
        Func newNode = (Func) node;
        newNode.returnType().visit(this);
        for (Node typeParameter : newNode.typeParameters()) {
            typeParameter.visit(this);
        }
        for (Node parameter : newNode.parameters()) {
            parameter.visit(this);
        }
    }

    public void visitProtocolFuncDecl(Node node) {
        this.visitFunc(node);
    }

    public void visitFuncParameter(Node node) {
        node.hmType().visit(this);
    }

    public void visitFuncDef(Node node) {
        this.visitFunc(node);
        node.body().visit(this);
    }

    public void visitNativeFunc(Node node) {
        this.visitFunc(node);
    }

    public void visitNativeFuncInstance(Node node) {
        NativeFuncInstance newNode = (NativeFuncInstance) node;
        this.visitFunc(newNode);
        newNode.func1().visitImplementationData.accept(newNode.implementationData(), this);
    }

    public void visitBlock(Node node) {
        for (Node statement :((Block) node).statements()) {
            statement.visit(this);
        }
    }

    public Object visitCommaExpression(Node node) {
        for (HmValue expression : ((CommaExpression) node).list()) {
            expression.visit(this);
        }
        return null;
    }

    public void visitProtocolRef(Node node) {
    }

    public void visitProtocolDecl(Node node) {
        ProtocolDecl newNode = (ProtocolDecl) node;
        for (ProtocolRef protocol1 : newNode.extend) {
            protocol1.visit(this);
        }
        for (ProtocolFuncDecl signature : newNode.signatures()) {
            signature.visit(this);
        }
    }

    public void visitTypeRef(Node node) {
        for (Node typeArgument : ((TypeRef) node).typeArguments) {
            typeArgument.visit(this);
        }
    }

    public void visitNativeType(Node node) {
        for (Node typeParameter : ((NativeType) node).typeParameters) {
            typeParameter.visit(this);
        }
    }

    public void visitNativeTypeInstance(Node node) {
        NativeTypeInstance newNode = (NativeTypeInstance) node;
        newNode.hmType().visit(this);
        for (Node typeArgument : newNode.typeArguments) {
            typeArgument.visit(this);
        }
    }

    public void visitTypeDef(Node node) {
        for (Node typeParameter : ((TypeDef) node).typeParameters) {
            typeParameter.visit(this);
        }
        node.hmType().visit(this);
    }

    public void visitStructType(Node node) {
        StructType newNode = (StructType) node;
        for (Node typeParameter : newNode.typeParameters) {
            typeParameter.visit(this);
        }
        for (Field field : newNode.fields()) {
            field.visit(this);
        }
    }

    public void visitTypeVariable(Node node) {
        Node.visit(((TypeVariable) node).protocol1(), this);
    }

    public void visitConstexprTypeParameter(Node node) {
        ((ConstexprTypeParameter) node).hmType().visit(this);
    }

    public void visitField(Node node) {
        node.hmType().visit(this);
    }

    public void visitEnumType(Node node) {
        EnumType newNode = (EnumType) node;
        newNode.baseType().visit(this);
        for (EnumMember member : newNode.members()) {
            member.visit(this);
        }
    }

    public void visitEnumMember(Node node) {
        Node.visit(((EnumMember) node).value, this);
    }

    public void visitEnumLiteral(Node node) {}

    public void visitElementalType(Node node) {
        ((HmType) node).elementType().visit(this);
    }

    public void visitReferenceType(Node node) {
        this.visitElementalType(node);
    }

    public void visitPtrType(Node node) {
        this.visitReferenceType(node);
    }

    public void visitArrayRefType(Node node) {
        this.visitReferenceType(node);
    }

    public void visitArrayType(Node node) {
        ArrayType newNode = (ArrayType) node;
        this.visitElementalType(newNode);
        newNode.numElements().visit(this);
    }

    public void visitVariableDecl(Node node) {
        node.hmType().visit(this);
        Node.visit(((VariableDecl) node).initializer(), this);
    }

    public void visitAssignment(Node node) {
        Assignment newNode = (Assignment) node;
        newNode.lhs().visit(this);
        newNode.rhs().visit(this);
        Node.visit(newNode.hmType(), this);
    }

    public void visitReadModifyWriteExpression(Node node) {
        ReadModifyWriteExpression newNode = (ReadModifyWriteExpression) node;
        newNode.lValue().visit(this);
        newNode.oldValueVar.visit(this);
        newNode.newValueVar.visit(this);
        newNode.newValueExp.visit(this);
        newNode.resultExp.visit(this);
    }

    public void visitDereferenceExpression(Node node) {
        ((DereferenceExpression) node).ptr().visit(this);
    }

    public void handlePropertyAccessExpression(Node node) {
        PropertyAccessExpression newNode = (PropertyAccessExpression) node;
        Node.visit(newNode.baseType, this);
        Node.visit(newNode.callForGet, this);
        Node.visit(newNode.resultTypeForGet, this);
        Node.visit(newNode.callForAnd, this);
        Node.visit(newNode.resultTypeForAnd, this);
        Node.visit(newNode.callForSet, this);
    }

    public void visitDotExpression(Node node) {
        node.struct1().visit(this);
        this.handlePropertyAccessExpression(node);
    }

    public void visitIndexExpression(Node node) {
        IndexExpression newNode = (IndexExpression) node;
        newNode.array().visit(this);
        newNode.index.visit(this);
        this.handlePropertyAccessExpression(newNode);
    }

    public void visitMakePtrExpression(Node node) {
        ((MakePtrExpression) node).lValue().visit(this);
    }

    public void visitMakeArrayRefExpression(Node node) {
        MakeArrayRefExpression newNode = (MakeArrayRefExpression) node;
        newNode.lValue().visit(this);
        Node.visit(newNode.numElements, this);
    }

    public void visitConvertPtrToArrayRefExpression(Node node) {
        ((ConvertPtrToArrayRefExpression) node).lValue().visit(this);
    }

    public void visitVariableRef(Node node) {}

    public void visitIfStatement(Node node) {
        IfStatement newNode = (IfStatement) node;
        newNode.conditional().visit(this);
        newNode.body().visit(this);
        Node.visit(newNode.elseBody(), this);
    }

    public void visitWhileLoop(Node node) {
        WhileLoop newNode = (WhileLoop) node;
        newNode.conditional().visit(this);
        newNode.body().visit(this);
    }

    public void visitDoWhileLoop(Node node) {
        DoWhileLoop newNode = (DoWhileLoop) node;
        newNode.body().visit(this);
        newNode.conditional().visit(this);
    }

    public void visitForLoop(Node node) {
        ForLoop newNode = (ForLoop) node;
        Node.visit(newNode.initialization(), this);
        Node.visit(newNode.condition(), this);
        Node.visit(newNode.increment(), this);
        newNode.body().visit(this);
    }

    public void visitSwitchStatement(Node node) {
        SwitchStatement newNode = (SwitchStatement) node;
        newNode.value().visit(this);
        for (SwitchCase switchCase : newNode.switchCases()) {
            switchCase.visit(this);
        }
    }

    public void visitSwitchCase(Node node) {
        SwitchCase newNode = (SwitchCase) node;
        Node.visit(newNode.value(), this);
        newNode.body().visit(this);
    }

    public void visitReturn(Node node) {
        Node.visit(((Return) node).value(), this);
    }

    public void visitContinue(Node node) {}

    public void visitBreak(Node node) {}

    public void visitTrapStatement(Node node) {}

    public void visitGenericLiteral(Node node) {
        node.hmType().visit(this);
    }

    public void visitGenericLiteralType(Node node) {
        Node.visit(node.hmType(), this);
        ((GenericLiteralType) node).preferredType.visit(this);
    }

    public void visitNullLiteral(Node node) {
        node.hmType().visit(this);
    }

    public void visitBoolLiteral(Node node) {}

    public void visitNullType(Node node) {
        Node.visit(node.hmType(), this);
    }

    public void visitCallExpression(Node node) {
        CallExpression newNode = (CallExpression) node;
        for (Node typeArgument : newNode.typeArguments()) {
            typeArgument.visit(this);
        }
        for (HmValue argument : newNode.argumentList()) {
            Node.visit((argument), this);
        }
        Consumer<List<Node>> handleTypeArguments = new Consumer<List<Node>>() {
            @Override
            public void accept(List<Node> actualTypeArguments) {
                if (actualTypeArguments.size() != 0) {
                    for (Node argument : actualTypeArguments) {
                        argument.visit(Visitor.this);
                    }
                }
            }
        };

        handleTypeArguments.accept(newNode.actualTypeArguments);
        handleTypeArguments.accept(newNode.instantiatedActualTypeArguments);
        Node.visit(newNode.nativeFuncInstance, this);
        Node.visit(newNode.returnType(), this);
        Node.visit(newNode.resultType, this);
    }

    public void visitLogicalNot(Node node) {
        ((LogicalNot) node).operand().visit(this);
    }

    public void visitLogicalExpression(Node node) {
        LogicalExpression newNode = (LogicalExpression) node;
        newNode.left().visit(this);
        newNode.right().visit(this);
    }

    public void visitFunctionLikeBlock(Node node) {
        FunctionLikeBlock newNode = (FunctionLikeBlock) node;
        Node.visit(newNode.returnType(), this);
        for (Node argument : newNode.argumentList()) {
            argument.visit(this);
        }
        for (FuncParameter parameter : newNode.parameters()) {
            parameter.visit(this);
        }
        newNode.body().visit(this);
        Node.visit(newNode.resultType, this);
    }

    public void visitAnonymousVariable(Node node) {
        Node.visit(((AnonymousVariable) node).hmType(), this);
    }

    public void visitIdentityExpression(Node node) {
        ((Expression) node).target().visit(this);
    }

    public void visitTypeOrVariableRef(Node node) {}

    @Override
    public Map<String, Function<Base, Object>> mapReturn() {
        Map<String, Function<Base, Object>> methods = super.mapReturn();
        methods.put("visitProgram", base -> {
            visitProgram((Node) base);
            return null;});
        methods.put("visitProtocolFuncDecl", base -> {
            visitProtocolFuncDecl((Node) base);
            return null;
        });
        methods.put("visitFuncParameter", base -> {
            visitFuncParameter((Node) base);
            return null;
        });
        methods.put("visitFuncDef", base -> {
            visitFuncDef((Node) base);
            return null;
        });
        methods.put("visitNativeFunc", base -> {
            visitNativeFunc((Node) base);
            return null;
        });
        methods.put("visitNativeFuncInstance", base -> {
            visitNativeFuncInstance((Node) base);
            return null;
        });
        methods.put("visitBlock", base -> {
            visitBlock((Node) base);
            return null;
        });
        methods.put("visitCommaExpression", base -> {
            visitCommaExpression((Node) base);
            return null;
        });
        methods.put("visitProtocolRef", base -> {
            visitProtocolRef((Node) base);
            return null;
        });
        methods.put("visitProtocolDecl", base -> {
            visitProtocolDecl((Node) base);
            return null;
        });
        methods.put("visitTypeRef", base -> {
            visitTypeRef((Node) base);
            return null;
        });
        methods.put("visitNativeType", base -> {
            visitNativeType((Node) base);
            return null;
        });
        methods.put("visitTypeDef", base -> {
            visitTypeDef((Node) base);
            return null;
        });
        methods.put("visitStructType", base -> {
            visitStructType((Node) base);
            return null;
        });
        methods.put("visitTypeVariable", base -> {
            visitTypeVariable((Node) base);
            return null;
        });
        methods.put("visitConstexprTypeParameter", base -> {
            visitConstexprTypeParameter((Node) base);
            return null;
        });
        methods.put("visitField", base -> {
            visitField((Node) base);
            return null;
        });
        methods.put("visitEnumType", base -> {
            visitEnumType((Node) base);
            return null;
        });
        methods.put("visitEnumMember", base -> {
            visitEnumMember((Node) base);
            return null;
        });
        methods.put("visitEnumLiteral", base -> {
            visitEnumLiteral((Node) base);
            return null;
        });
        methods.put("visitPtrType", base -> {
            visitPtrType((Node) base);
            return null;
        });
        methods.put("visitArrayRefType", base -> {
            visitArrayRefType((Node) base);
            return null;
        });
        methods.put("visitArrayType", base -> {
            visitArrayType((Node) base);
            return null;
        });
        methods.put("visitVariableDecl", base -> {
            visitVariableDecl((Node) base);
            return null;
        });
        methods.put("visitAssignment", base -> {
            visitAssignment((Node) base);
            return null;
        });
        methods.put("visitReadModifyWriteExpression", base -> {
            visitReadModifyWriteExpression((Node) base);
            return null;
        });
        methods.put("visitDereferenceExpression", base -> {
            visitDereferenceExpression((Node) base);
            return null;
        });
        methods.put("visitDotExpression", base -> {
            visitDotExpression((Node) base);
            return null;
        });
        methods.put("visitIndexExpression", base -> {
            visitIndexExpression((Node) base);
            return null;
        });
        methods.put("visitMakePtrExpression", base -> {
            visitMakePtrExpression((Node) base);
            return null;
        });
        methods.put("visitMakeArrayRefExpression", base -> {
            visitMakeArrayRefExpression((Node) base);
            return null;
        });
        methods.put("visitConvertPtrToArrayRefExpression", base -> {
            visitConvertPtrToArrayRefExpression((Node) base);
            return null;
        });
        methods.put("visitVariableRef", base -> {
            visitVariableRef((Node) base);
            return null;
        });
        methods.put("visitIfStatement", base -> {
            visitIfStatement((Node) base);
            return null;
        });
        methods.put("visitWhileLoop", base -> {
            visitWhileLoop((Node) base);
            return null;
        });
        methods.put("visitDoWhileLoop", base -> {
            visitDoWhileLoop((Node) base);
            return null;
        });

        methods.put("visitForLoop", base -> {
            visitForLoop((Node) base);
            return null;
        });
        methods.put("visitSwitchStatement", base -> {
            visitSwitchStatement((Node) base);
            return null;
        });
        methods.put("visitSwitchCase", base -> {
            visitSwitchCase((Node) base);
            return null;
        });
        methods.put("visitReturn", base -> {
            visitReturn((Node) base);
            return null;
        });

        methods.put("visitContinue", base -> {
            visitContinue((Node) base);
            return null;
        });
        methods.put("visitBreak", base -> {
            visitBreak((Node) base);
            return null;
        });
        methods.put("visitTrapStatement", base -> {
            visitTrapStatement((Node) base);
            return null;
        });
        methods.put("visitGenericLiteral", base -> {
            visitGenericLiteral((Node) base);
            return null;
        });

        methods.put("visitGenericLiteralType", base -> {
            visitGenericLiteralType((Node) base);
            return null;
        });
        methods.put("visitNullLiteral", base -> {
            visitNullLiteral((Node) base);
            return null;
        });
        methods.put("visitBoolLiteral", base -> {
            visitBoolLiteral((Node) base);
            return null;
        });
        methods.put("visitNullType", base -> {
            visitNullType((Node) base);
            return null;
        });

        methods.put("visitCallExpression", base -> {
            visitCallExpression((Node) base);
            return null;
        });
        methods.put("visitLogicalNot", base -> {
            visitLogicalNot((Node) base);
            return null;
        });
        methods.put("visitLogicalExpression", base -> {
            visitLogicalExpression((Node) base);
            return null;
        });
        methods.put("visitFunctionLikeBlock", base -> {
            visitFunctionLikeBlock((Node) base);
            return null;
        });

        methods.put("visitAnonymousVariable", base -> {
            visitAnonymousVariable((Node) base);
            return null;
        });
        methods.put("visitIdentityExpression", base -> {
            visitIdentityExpression((Node) base);
            return null;
        });
        methods.put("visitTypeOrVariableRef", base -> {
            visitTypeOrVariableRef((Node) base);
            return null;
        });
        methods.put("visitIndexExpression", base -> {
            visitIndexExpression((Node) base);
            return null;
        });

        return methods;
    }
}

class PropertyAccessExpression extends Expression {
    public Expression base = new Expression(null);
    private Boolean _isLValue = null;
    private String _notLValueReason = null;
    public CallExpression callForGet = null;
    public CallExpression callForAnd = null;
    public CallExpression callForSet = null;
    public HmType resultTypeForGet = null;
    public HmType resultTypeForAnd = null;
    public WTypeError errorForSet = null;
    public HmType baseType = null;
    public List<Func> possibleGetOverloads = new ArrayList<>();
    public List<Func> possibleSetOverloads = new ArrayList<>();
    public List<Func> possibleAndOverloads = new ArrayList<>();

    PropertyAccessExpression(LexerToken origin, Expression base) {
        super(origin);
        this.base = base;
        this._isLValue = null;
        this.addressSpace(null);
        this._notLValueReason = null;
    }

    public HmType resultType() {
        return this.resultTypeForGet != null ? this.resultTypeForGet : this.resultTypeForAnd;
    }

    public void isLValue(boolean newValue) {
        if (!this.isBecome) {
            this._isLValue = newValue;
        }
    }

    public boolean isLValue() {
        if (this.isBecome) {
            return this.target().isLValue();
        } else {
            return this._isLValue;
        }
    }

    public void notLValueReason(String newValue) {
        if (!this.isBecome) {
            this._notLValueReason = newValue;
        }
    }

    @Override
    public String notLValueReason() {
        if (this.isBecome) {
            return super.notLValueReason();
        } else {
            return this._notLValueReason;
        }
    }

    public String getFuncName() {
        return "";
    }

    public String andFuncName() {
        return "";
    }

    public String setFuncName() {
        return "";
    }

    public Expression rewriteAfterCloning() {
        // At this point, node.base.isLValue will only return true if it's possible to get a pointer to it,
        // since we will no longer see any DotExpressions or IndexExpressions. Also, node is a newly created
        // node and everything beneath it is newly created, so we can edit it in-place.
        if ((this.base.isLValue() || this.baseType.isRef()) && (this.callForAnd != null)) {
            return new DereferenceExpression(this.origin, this.callForAnd, this.resultType(), ((PtrType) this.callForAnd.resultType).addressSpace());
        }

        if (this.callForGet != null) {
            return this.callForGet;
        }

        if (this.callForAnd == null) {
            throw new Error(this.origin.originString() + ":Property access without getter and ander:" + this);
        }
        AnonymousVariable anonVar = new AnonymousVariable(this.origin, this.baseType);
        this.callForAnd.argumentList().set(0, ((HmType) this.baseType.unifyNode()).argumentForAndOverload(this.origin, VariableRef.wrap(anonVar)));
        return new CommaExpression(this.origin,
                Arrays.asList(anonVar,new Assignment(this.origin, VariableRef.wrap(anonVar), this.base, this.baseType),
                new DereferenceExpression(this.origin, this.callForAnd, this.resultType(), ((PtrType) this.callForAnd.resultType).addressSpace())));
    }

    public void updateCalls() {
        if (this.callForGet != null) {
            this.callForGet.argumentList().set(0, this.base);
        }
        if (this.callForAnd != null) {
            this.callForAnd.argumentList().set(0, this.baseType.argumentForAndOverload(this.origin, this.base));
        }
        if (this.callForSet != null) {
            this.callForSet.argumentList().set(0, this.base);
        }
    }

    public CallExpression emitGet(VariableRef base) {
        PropertyAccessExpression result = (PropertyAccessExpression) this.visit(new Rewriter());
        result.base = base;
        result.updateCalls();
        return (CallExpression) result.rewriteAfterCloning();
    }

    public CallExpression emitSet(VariableRef base, VariableRef value) {
        PropertyAccessExpression result = (PropertyAccessExpression) this.visit(new Rewriter());
        if (result.callForSet == null) {
            throw new WTypeError(this.origin.originString(), "Cannot emit set because: " + this.errorForSet.typeErrorMessage);
        }
        result.base = base;
        result.updateCalls();
        if (result.callForSet.argumentList().get(result.callForSet.argumentList().size() - 1) == null) {
            result.callForSet.argumentList().set(result.callForSet.argumentList().size() - 1, value);
        } else {
            result.callForSet.argumentList().add(value);
        }
        return result.callForSet;
    }
}

class HmProtocol extends Node {
    HmProtocol(LexerToken origin, String name) {
        super();
        this.origin = origin;
        this.name(name);
    }

    public String kind() {
        return "Protocol";
    }

    public void kind(String newValue) {}

    @Override
    public String toString() {
        return this.name();
    }
}

class ProtocolDecl extends HmProtocol {
    public List<ProtocolRef> extend = new ArrayList<>();
    private List<ProtocolFuncDecl> _signatures = new ArrayList<>();
    private Map<String, List<ProtocolFuncDecl>> _signatureMap = new HashMap<>();
    private TypeVariable _typeVariable;

    ProtocolDecl(LexerToken origin, String name) {
        super(origin, name);
        this._typeVariable = new TypeVariable(origin, name, null);
    }

    public void addExtends(ProtocolRef protocol1) {
        this.extend.add(protocol1);
    }

    public void add(ProtocolFuncDecl signature) {
        if (!(signature instanceof ProtocolFuncDecl)) {
            throw new Error("Signature isn't a ProtocolFuncDecl but a" + ((Base) signature).getClass().getSimpleName());
        }
        signature.protocolDecl = this;
        this._signatures.add(signature);
        List<ProtocolFuncDecl> overloads = this._signatureMap.get(signature.name());
        if (overloads == null) {
            overloads = new ArrayList<>();
            this._signatureMap.put(signature.name(), overloads);
        }
        overloads.add(signature);
        this._signatureMap.put(signature.name(), overloads);
    }

    public List<ProtocolFuncDecl> signatures() {
        return this._signatures;
    }

    List<ProtocolFuncDecl> signaturesByName(String name) {
        return this._signatureMap.get(name);
    }

    public TypeVariable typeVariable() {
        return this._typeVariable;
    }

    public List<ProtocolFuncDecl> signaturesByNameWithTypeVariable(String name, TypeVariable typeVariable) {
        Substitution substitution = new Substitution(Collections.singletonList(typeVariable()), Collections.singletonList(typeVariable));
        List<ProtocolFuncDecl> result = this.signaturesByName(name);
        if (result == null) {
            return null;
        }
        return result.stream().map(decl -> (ProtocolFuncDecl) decl.visit(substitution)).collect(Collectors.toList());
    }

    public VerifyResult inherits(ProtocolRef otherProtocol) {
        if (otherProtocol == null) {
            return new VerifyResult(true);
        }
        ProtocolDecl protocolDecl = otherProtocol.protocolDecl;
        for (ProtocolFuncDecl otherSignature : protocolDecl.signatures()) {
            List<ProtocolFuncDecl> signatures = this.signaturesByName(otherSignature.name());
            if (signatures == null) {
                return new VerifyResult(
                        false,
                        "Protocol " + this.name() + " does not have a function named " + otherSignature.name() + "(looking at signature " + otherSignature.toString() + ")"
                );
            }
            ResolveOverloadResult overload = AllFunction.resolveOverloadImpl(
                    signatures.stream().map(protocolFuncDecl -> (Func) protocolFuncDecl).collect(Collectors.toList()),
                    new ArrayList<Node>(),
                    otherSignature.parameterTypes().stream().map(hmType -> (Node) hmType).collect(Collectors.toList()),
                    (TypeRef) otherSignature.returnTypeForOverloadResolution(),
                    false
            );
            if (overload.func1 == null) {
                String tmp =
                        overload.failures.size() != 0 ? "(tried: " + overload.failures.stream().map(OverloadResolutionFailure::toString).collect(Collectors.joining("; ")) : "";
                return new VerifyResult(false, "Did not find matching signature for " + otherSignature.toString() + " in " + name() + tmp);
            }
            HmType substitutedReturnType = overload.func1.returnType().substituteToUnification(overload.func1.typeParameters(), overload.unificationContext);
            if (substitutedReturnType.equals(otherSignature.returnType()) == null) {
                return new VerifyResult(false, "Return type mismatch between " + otherSignature.returnType().toString() + " and " + substitutedReturnType);
            }
        }
        return new VerifyResult(true);
    }

    public VerifyResult hasHeir(HmType type) {
        Substitution substitution = new Substitution(Collections.singletonList(this._typeVariable), Collections.singletonList(type));
        List<ProtocolFuncDecl> signatures = this.signatures();
        for (ProtocolFuncDecl originalSignature : signatures) {
            ProtocolFuncDecl signature = (ProtocolFuncDecl) originalSignature.visit(substitution);
            ResolveOverloadResult overload = AllFunction.resolveOverloadImpl(
                    signature.possibleOverloads,
                    signature.typeParameters(),
                    signature.parameterTypes().stream().map(hmType -> (Node) hmType).collect(Collectors.toList()),
                    (TypeRef) signature.returnTypeForOverloadResolution(),
                    false);
            if (overload.func1 == null) {
                String tmp =
                        overload.failures.size() != 0 ?
                                "(tried: " + overload.failures.stream().map(OverloadResolutionFailure::toString).collect(Collectors.joining("; ")) :
                                "";
                return new VerifyResult(
                        false,
                        "Did not find matching signature for " + originalSignature.toString() + " (at " +
                                originalSignature.origin.originString() +
                                " with type " + type.toString() + tmp
                );
            }
            HmType substitutedReturnType = overload.func1.returnType().substituteToUnification(overload.func1.typeParameters(), overload.unificationContext);
            if (substitutedReturnType.equals(signature.returnType()) == null) {
                return new VerifyResult(
                        false,
                        "At signature " + originalSignature.toString() + " (at " +
                        originalSignature.origin.originString() +
                        "): return type mismatch between " + signature.returnType().toString() + " and " + substitutedReturnType.toString() + " in found function " + overload.func1.toDeclString());
            }
        }
        return new VerifyResult(true);
    }

    public String toString(){
        return "protocol " + this.name() +" {" + this.signatures().stream().map(decl -> decl.toString()).collect(Collectors.joining("; ")) + ";" + "}";
    }

}

class AnonymousVariable extends HmValue {
    public Integer _index = null;
    public int index = 0;
    // You have to initialize the variable's value before use, but that could be quite late.
    AnonymousVariable(LexerToken origin, HmType type) {
        super();
        this.origin = origin;
        this.index = Constants.anonymousVariableCount;
        Constants.anonymousVariableCount += 1;
        this.hmType(type);
    }

    public String name() {
        return "anonVar<" + this.index + ">";
    }

    public void name(String newValue) {}

    @Override
    public String toString() {
        return this.name();
    }
}

// Note that we say that T[] is "the same type" as T[c] for any T, c. This greatly simplifies the
// language.
class ArrayRefType extends ReferenceType {
    ArrayRefType(LexerToken origin, String addressSpace, HmType elementType) {
        super(origin, addressSpace, elementType);
    }

    public boolean unifyImpl(UnificationContext unificationContext, Node other) {
        if (!((HmType) other).isArrayRef()) {
            return false;
        }

        if (!this.addressSpace().equals(((ArrayRefType) other).addressSpace())) {
            return false;
        }

        return this.elementType().unify(unificationContext, ((ArrayRefType) other).elementType());
    }

    @Override
    public boolean isArrayRef() {
        return true;
    }

    public HmValue argumentForAndOverload(LexerToken origin, HmValue value) {
        return value;
    }

    public ArrayRefType argumentTypeForAndOverload(LexerToken origin) {
        return this;
    }

    @Override
    public String toString() {
        return this.elementType() == null ? "" : this.elementType().toString() + "[] " + this.addressSpace();
    }
}

class ArrayType extends HmType {
    private HmType _elementType;
    private Expression _numElements;

    ArrayType(LexerToken origin, HmType elementType, Expression numElements) {
        super();
        if (numElements == null) {
            throw new Error("null numElements");
        }
        this.origin = origin;
        this._elementType = elementType;
        this._numElements = numElements;
    }

    @Override
    public HmType elementType() {
        return this._elementType;
    }

    @Override
    public Expression numElements() {
        return this._numElements;
    }

    @Override
    public boolean isPrimitive() {
        return this.elementType().isPrimitive();
    }

    public void setIsPrimitive(boolean newValue) {}

    @Override
    public boolean isArray() {
        return true;
    }

    public Integer numElementsValue() {
        if (this.numElements().isLiteral() == false) {
            throw new WTypeError("numElements is not a literal: " + (this.numElements() != null ? this.numElements().toString() : ""));
        }
        int value = WSLNumber.objectToInt(this.numElements().value());
        return value;
    }

    public void setNumElementsValue() {}

    @Override
    public String toString() {
        return this.elementType() + "[" + (this.numElements() == null ? "" : this.numElements().toString()) + "]";
    }

    @Override
    public int size() {
        return this.elementType().size() * this.numElementsValue();
    }

    @Override
    public void size(int newVaue) {}

    @Override
    public TypeRef populateDefaultValue(EBuffer buffer, int offset) {
        int size = this.numElementsValue() == null ? 0 : this.numElementsValue();
        for (int i = 0; i < size; i++) {
            this.elementType().populateDefaultValue(buffer, offset + i * (this.elementType().size()));
        }
        return null;
    }

    @Override
    public boolean unify(UnificationContext unificationContext, Node other) {
        if (!(other instanceof ArrayType)) {
            return false;
        }

        if (!(this.numElements().unify(unificationContext, ((HmType) other).numElements()))) {
            return false;
        }

        return this.elementType().unify(unificationContext, ((HmType) other).elementType() == null ? new Node() : ((HmType) other).elementType());
    }

    @Override
    public HmValue argumentForAndOverload(LexerToken origin, HmValue value) {
        MakeArrayRefExpression result = new MakeArrayRefExpression(origin, value);
        result.numElements = this.numElements();
        return result;
    }

    @Override
    public ReferenceType argumentTypeForAndOverload(LexerToken origin) {
        return new ArrayRefType(origin, "thread", (TypeRef) this.elementType());
    }
}

class Assignment extends Expression {
    private HmValue _lhs;
    private HmValue _rhs;

    Assignment(LexerToken origin, HmValue lhs, HmValue rhs, HmType type) {
        super(origin);
        this._lhs = lhs;
        this._rhs = rhs;
        this.hmType(type);
    }

    public HmValue lhs() {
        return this._lhs;
    }

    public HmValue rhs() {
        return this._rhs;
    }

    @Override
    public String toString() {
        if (this.isBecome) {
            return "(" + this.target().toString() + ")";
        } else {
            return this.lhs().toString() + " = " + this.rhs().toString();
        }
    }
}

class AutoWrapper extends Rewriter {
    public VariableRef visitVariableRef(Node node) {
        return (VariableRef) node;
    }

    public TypeRef visitTypeRef(Node node) {
        return (TypeRef) node;
    }

    public VariableRef visitConstexprTypeParameter2(Node node) {
        return VariableRef.wrap((HmValue) node);
    }

    public VariableRef visitFuncParameter2(Node node) {
        return VariableRef.wrap((HmValue) node);
    }

    public VariableRef visitVariableDecl2(Node node) {
        return VariableRef.wrap((HmValue) node);
    }

    public TypeRef visitStructType2(Node node) {
        return TypeRef.wrap((HmType) node);
    }

    public TypeRef visitNativeType2(Node node) {
        return TypeRef.wrap((HmType) node);
    }

    public TypeRef visitTypeVariable2(Node node) {
        return TypeRef.wrap((HmType) node);
    }

    public GenericLiteralType visitGenericLiteralType(Node node) {
        return (GenericLiteralType) node;
    }

    public NullType visitNullType(Node node) {
        return (NullType) node;
    }

    @Override
    public Map<String, Function<Base, Object>> mapReturn() {
        Map<String, Function<Base, Object>> methods = super.mapReturn();
        methods.put("visitConstexprTypeParameter", base -> visitConstexprTypeParameter2((Node) base));
        methods.put("visitFuncParameter", base -> visitFuncParameter2((Node) base));
        methods.put("visitVariableDecl", base -> visitVariableDecl2((Node) base));
        methods.put("visitStructType", base -> visitStructType2((Node) base));
        methods.put("visitNativeType", base -> visitNativeType2((Node) base));
        methods.put("visitTypeVariable", base -> visitTypeVariable2((Node) base));
        methods.put("visitNullType", base -> visitNullType((Node) base));
        return methods;
    }
}
// Block class
class Block extends Node {

    private String _originStr;
    private List<Node> _statements;

    Block(String originStr) {
        super();
        this._originStr = originStr;
        this._statements = new ArrayList<>();

    }

    public String originStr() {
        return this._originStr;
    }

    public void add(Node statement) {
        this._statements.add(statement);
    }

    public List<Node> statements() {
        return this._statements;
    }

    @Override
    public String toString() {
        if (this._statements.size() == 0) {
            return "{ }";
        }
        return "{ " + this._statements.stream().map(Node::toString).collect(Collectors.joining("; ")) + "; }";
    }

}

class BoolLiteral extends Expression {
    BoolLiteral(LexerToken origin, boolean value) {
        super(origin);
        this._value = value;
    }

    @Override
    public Object value() {
        return this._value;
    }

    @Override
    public void value(Object newValue) {}

    @Override
    public boolean isConstexpr() {
        return true;
    }

    public void isConstexpr(boolean newValue) {}

    @Override
    public String toString() {
        return String.valueOf(value());
    }
}
// Break class
class Break extends Node {
    Break(LexerToken origin) {
        super();
        this.origin = origin;
    }

    @Override
    public String toString() {
        return "break";
    }
}

class CallExpression extends Expression {
    public Node func1;
    public Node resultType;
    public List<HmType> argumentTypes = new ArrayList<>();
    public List<Node> actualTypeArguments = new ArrayList<>();
    public List<Node> instantiatedActualTypeArguments = new ArrayList<>();
    public List<Func> possibleOverloads = new ArrayList<>();
    public Func nativeFuncInstance;
    private List<Node> _typeArguments = new ArrayList<>();
    private List<HmValue> _argumentList = new ArrayList<>();
    private boolean _isCast = false;
    private TypeRef _returnType;

    CallExpression(LexerToken origin, String name, List<? extends Node> typeArguments, List<? extends HmValue> argumentList) {
        super(origin);
        name(name);
        this.typeArguments(typeArguments.stream().map(data -> (Node) data).collect(Collectors.toList()));
        this.argumentList(argumentList.stream().map(data -> (HmValue) data).collect(Collectors.toList()));
        this.func1 = null;
        this._isCast = false;
        this._returnType = null;
    }

    public List<Node> typeArguments() {
        return this._typeArguments;
    }

    public void typeArguments(List<Node> newValue) {
        this._typeArguments = newValue;
    }

    public List<HmValue> argumentList() {
        return this._argumentList;
    }

    public void argumentList(List<HmValue> newValue) {
        this._argumentList = newValue;
    }

    public boolean isCast() {
        return this._isCast;
    }

    public TypeRef returnType() {
        return this._returnType;
    }

    public static CallResolveResult resolve(
            LexerToken origin,
            List<Func> possibleOverloads,
            List<Node> typeParametersInScope,
            String name,
            List<HmType> typeArguments,
            List<Expression> argumentList,
            List<HmType> argumentTypes,
            TypeRef returnType
    ) {
        CallExpression call = new CallExpression(origin, name, typeArguments, argumentList);
        call.argumentTypes = argumentTypes.stream().map(argument -> (HmType) argument.visit(new AutoWrapper())).collect(Collectors.toList());
        call.possibleOverloads = possibleOverloads;
        if (returnType != null) {
            call.setCastData(returnType);
        }
        return new CallResolveResult(call, call.resolve(possibleOverloads, typeParametersInScope, typeArguments));
    }

    public HmType resolve(List<Func> possibleOverloads, List<Node> typeParametersInScope, List<? extends Node> typeArguments) {
        if (possibleOverloads == null || possibleOverloads.size() == 0) {
            throw new WTypeError(this.origin.originString(), " Did not find any functions named " + this.name());
        }
        ResolveOverloadResult overload = null;
        ArrayList<OverloadResolutionFailure> failures = new ArrayList<>();
        for (Node typeParameter : typeParametersInScope) {
            if (!(typeParameter instanceof TypeVariable)) {
                continue;
            }
            if (((TypeVariable) typeParameter).protocol1() == null) {
                continue;
            }
            List<ProtocolFuncDecl> signatures = ((TypeVariable) typeParameter).protocol1().protocolDecl.signaturesByNameWithTypeVariable(this.name(), (TypeVariable) typeParameter);
            if (signatures == null) {
                continue;
            }
            overload = AllFunction.resolveOverloadImpl(signatures.stream().map(protocolFuncDecl -> (Func)protocolFuncDecl).collect(Collectors.toList()), this.typeArguments(), this.argumentTypes, this.returnType(), false);
            if (overload.func1 != null) {
                break;
            }
            failures.addAll(overload.failures);
            overload = null;
        }
        if (overload == null) {
            overload = AllFunction.resolveOverloadImpl(possibleOverloads, this.typeArguments(), this.argumentTypes, this.returnType(), false);
            if (overload.func1 == null) {
                failures.addAll(overload.failures);
                String message = "{Did not find function named" + this.name() + " for call with }";
                if (this.typeArguments().size() > 0) {
                    message += "{type arguments <" + this.typeArguments().stream().map(Node::toString).collect(Collectors.joining(";")) + "> and }";
                }
                message += "{argument types (" + this.argumentTypes.stream().map(Node::toString).collect(Collectors.joining(";")) + ")";
                if (this.returnType() != null) {
                    message += "{ and return type " + this.returnType().toString() + "}";
                }
                if (failures.size() > 0) {
                    message += "{, but considered:\n" + failures.stream().map(OverloadResolutionFailure::toString).collect(Collectors.joining("\n")) + ")}";
                }
                throw new WTypeError(this.origin.originString() + message);
            }
        }
        for (int i = 0; i < typeArguments.size(); ++i) {
            Node typeArgumentType = typeArguments.get(i);
            Node typeParameter = overload.func1.typeParameters().get(i);
            if (!(typeParameter instanceof ConstexprTypeParameter)) {
                continue;
            }
            if (typeParameter.hmType().equalsWithCommit(typeArgumentType) == null) {
                throw new Error(
                        "At " +
                                this.origin.originString() +
                                " constexpr type argument and parameter types not equal: argument = " + typeArgumentType.toString() + ", parameter = " + typeParameter.hmType().toString()
                );
            }
        }
        for (int i = 0; i < this.argumentTypes.size(); ++i) {
            HmType argumentType = this.argumentTypes.get(i);
            Node parameterType = overload.func1.parameters().get(i).hmType().substituteToUnification(overload.func1.typeParameters(), overload.unificationContext);
            UnificationContext result = argumentType.equalsWithCommit(parameterType);
            if (result == null) {
                throw new Error(
                        "At " +
                                this.origin.originString() +
                                " argument and parameter types not equal after type argument substitution: argument = " + argumentType + ", parameter = " + parameterType
                );
            }
        }
        return this.resolveToOverload(overload);
    }

    public HmType resolveToOverload(ResolveOverloadResult overload) {
        this.func1 = overload.func1;
        this.actualTypeArguments = overload.typeArguments.stream().map(node -> (node instanceof HmType ? ((Node) node.visit(new AutoWrapper())) : node)).collect(Collectors.toList());
        this.instantiatedActualTypeArguments = this.actualTypeArguments;
        HmType result = overload.func1.returnType().substituteToUnification(overload.func1.typeParameters(), overload.unificationContext);
        if (result == null) {
            throw new Error("Null return type");
        }
        result = (HmType) result.visit(new AutoWrapper());
        this.resultType = result;
        return result;
    }

    public void becomeCast(HmType returnType) {
        this._returnType = new TypeRef(this.origin, this.name(), this._typeArguments);
        this._returnType.hmType(returnType);
        this.name("operator cast");
        this._isCast = true;
        this._typeArguments = new ArrayList<>();
    }

    public void setCastData(TypeRef returnType) {
        this._returnType = returnType;
        this._isCast = true;
    }

    @Override
    public String toString() {
        if (this.isBecome) {
            return "(" + this.target().toString() + ")";
        } else {
            String tmp = this.isCast() ? "operator " + this.returnType().toString() : this.name();
            String tmp2 =
                    (this.actualTypeArguments != null && this.actualTypeArguments.size() != 0) ? "<<" + this.actualTypeArguments.stream().map(Node::toString).collect(Collectors.joining(";")) + ">>" : "";
            return tmp + "<" + this.typeArguments().stream().map(Node::toString).collect(Collectors.joining(";")) + ">" + tmp2 + "(" + this.argumentList().stream()
                    .map(HmValue::toString).collect(Collectors
                            .joining(",")) + ")";
        }
    }
}

class Checker extends Visitor {
    private Program _program;
    private Node _currentStatement;
    private Set<String> _vertexEntryPoints = new HashSet<>();
    private Set<String> _fragmentEntryPoints = new HashSet<>();

    Checker(Program program) {
        super();
        this._program = program;
    }

    public void visitProgram(Node node) {
        Function<Node, Object> doStatement = (Node statement) -> {
            this._currentStatement = statement;
            statement.visit(this);
            return null;
        };
        for (HmType type : ((Program) node).types().values()) {
            doStatement.apply(type);
        }
        for (HmProtocol protocol : ((Program) node).protocols().values()) {
            doStatement.apply(protocol);
        }
        for (List<Func> funcs : ((Program) node).functions().values()) {
            for (Func func1 : funcs) {
                this.visitFunc(func1);
            }
        }
        for (List<Func> funcs : ((Program) node).functions().values()) {
            for (Func func1 : funcs) {
                doStatement.apply(func1);
            }
        }
    }

    public void checkShaderType(FuncDef node) {
        // FIXME: Relax these checks once we have implemented support for textures and samplers.
        if (node.typeParameters().size() != 0) {
            throw new WTypeError(node.origin.originString(), "Entry point " + node.name() + " must not have type arguments.");
        }
        switch (node.shaderType()) {
            case "vertex":
                if (this._vertexEntryPoints.contains(node.name())) {
                    throw new WTypeError(node.origin.originString(), "Duplicate vertex entry point name " + node.name());
                }
                this._vertexEntryPoints.add(node.name());
                break;
            case "fragment":
                if (this._fragmentEntryPoints.contains(node.name())) {
                    throw new WTypeError(node.origin.originString(), "Duplicate fragment entry point name " + node.name());
                }
                this._fragmentEntryPoints.add(node.name());
                break;

            default:
                break;
        }
    }

    public void checkOperatorOverload(Func func1, Function<String, List<Func>> resolveFuncs) {
        if (Lexer.textIsIdentifier(func1.name())) {
            return; // Not operator!
        }
        if (!func1.name().startsWith("operator")) {
            throw new Error("Bad operator overload name: " + func1.name());
        }
        TypeVariableTracker typeVariableTracker = new TypeVariableTracker();
        for (HmType parameterType : func1.parameterTypes()) {
            parameterType.visit(typeVariableTracker);
        }
        Node.visit(func1.returnTypeForOverloadResolution(), typeVariableTracker);
        for (Node typeParameter : func1.typeParameters()) {
            if (!typeVariableTracker.set().contains(typeParameter)) {
                throw new WTypeError(
                        typeParameter.origin.originString() + " Type parameter " + typeParameter + " to operator " + func1.toDeclString() + " is not inferrable from value parameters"
                );
            }
        }
        Function<String, Object> checkGetter = (kind) -> {
            int numExpectedParameters = kind.equals("index") ? Constants.WSL_2 : 1;
            if (func1.parameters().size() != numExpectedParameters) {
                throw new WTypeError(
                        func1.origin.originString() + "Incorrect number of parameters for " +
                                func1.name() +
                                " (expected " +
                                numExpectedParameters +
                                " , got " +
                                func1.parameters().size() +
                                ")"
                );
            }
            if (((HmType) (func1.parameterTypes().get(0).unifyNode())).isPtr()) {
                throw new WTypeError(func1.origin.originString() + "Cannot have getter for pointer type:" + func1.parameterTypes().get(0));
            }
            return null;
        };
        Function<String, Object> checkSetter = (kind) -> {
            int numExpectedParameters = kind.equals("index") ? Constants.WSL_3 : Constants.WSL_2;
            if (func1.parameters().size() != numExpectedParameters) {
                throw new WTypeError(
                        func1.origin.originString() + " Incorrect number of parameters for " + func1.name() + " (expected " + numExpectedParameters + ", got " + func1.parameters().size() + ")");
            }
            if (((HmType) func1.parameterTypes().get(0).unifyNode()).isPtr()) {
                throw new WTypeError(func1.origin.originString() + " Cannot have setter for pointer type: " + func1.parameterTypes().get(0).toString());
            }
            if (func1.returnType().equals(func1.parameterTypes().get(0)) == null) {
                throw new WTypeError(
                        func1.origin.originString() +
                                " First parameter type and return type of setter must match (parameter was " + func1.parameterTypes().get(0).toString() + " but return was " + func1.returnType().toString() + ")"
                );
            }
            HmType valueType = func1.parameterTypes().get(numExpectedParameters - 1);
            String getterName = func1.name().substring(0, func1.name().length() - 1);
            List<Func> getterFuncs = resolveFuncs.apply(getterName);
            if (getterFuncs == null) {
                throw new WTypeError(
                        func1.origin.originString() + " Every setter must have a matching getter, but did not find any function named " + getterName + " to match " + func1.name()
                );
            }
            List<HmType> argumentTypes = func1.parameterTypes().subList(0, numExpectedParameters - 1);
            ResolveOverloadResult overload = AllFunction.resolveOverloadImpl(
                    getterFuncs,
                    new ArrayList<>(),
                    argumentTypes.stream().map((data) -> (Node) data).collect(Collectors.toList()),
                    null,
                    false);
            if (overload.func1 == null) {
                String failureStr = overload.failures.stream().map(failure -> {
                            failure.toString();
                            return "";
                } ).collect(Collectors.joining(""));
                String tmp = !overload.failures.isEmpty() ? "; tried:\n" + failureStr  : "";
                throw new WTypeError(func1.origin.originString() + " Did not find function named " + func1.name() + " with arguments " + argumentTypes.stream().map(Object::toString).collect(Collectors.joining(","))  + tmp);
            }
            HmType resultType = overload.func1.returnType().substituteToUnification(overload.func1.typeParameters(), overload.unificationContext);
            if (resultType.equals(valueType) == null) {
                throw new WTypeError(
                        func1.origin.originString() + " Setter and getter must agree on value type (getter at " +
                                overload.func1.origin.originString() +
                                " says " + resultType.toString() + " while this setter says )" + valueType.toString()
                );
            }
            return null;
        };
        Function<String, Object> checkAnder = (kind) -> {
            int numExpectedParameters = kind.equals("index") ? Constants.WSL_2 : 1;
            if (func1.parameters().size() != numExpectedParameters) {
                throw new WTypeError(
                        func1.origin.originString() + " Incorrect number of parameters for " + func1.name() + " (expected " + numExpectedParameters + ",got " + func1.parameters().size() + ")"
                );
            }
            if (!((HmType) func1.returnType().unifyNode()).isPtr()) {
                throw new WTypeError(func1.origin.originString() + " Return type of ander is not a pointer:" + func1.returnType().toString());
            }
            if (!((HmType) func1.parameterTypes().get(0).unifyNode()).isRef()) {
                throw new WTypeError(func1.origin.originString() + " Parameter to ander is not a reference:" + func1.parameterTypes().get(0).toString());
            }
            return null;
        };

        switch (func1.name()) {
            case "operator cast":
                break;
            case "operator++":
            case "operator--":
                if (func1.parameters().size() != 1) {
                    throw new WTypeError(func1.origin.originString() + " Incorrect number of parameters for " + func1.name() + " (expected 1, got " + func1.parameters() + ")");
                }
                if (func1.parameterTypes().get(0).equals(func1.returnType()) == null) {
                    throw new WTypeError(
                            func1.origin.originString() + " Parameter type and return type must match for " + func1.name() + " (parameter is " +
                                    func1.parameterTypes().get(0) +
                                    " while return is " + func1.returnType().toString() + ")"
                    );
                }
                break;
            case "operator+":
            case "operator-":
                if (func1.parameters().size() != 1 && func1.parameters().size() != Constants.WSL_2) {
                    throw new WTypeError(
                            func1.origin.originString() + " Incorrect number of parameters for " + func1.name() + " (expected 1 or 2, got " + func1.parameters().size() + ")"
                    );
                }
                break;
            case "operator*":
            case "operator/":
            case "operator%":
            case "operator&":
            case "operator|":
            case "operator^":
            case "operator<<":
            case "operator>>":
                if (func1.parameters().size() != Constants.WSL_2) {
                    throw new WTypeError(func1.origin.originString() + " Incorrect number of parameters for " + func1.name() + " (expected 2, got " + func1.parameters().size() + ")");
                }
                break;
            case "operator~":
                if (func1.parameters().size() != 1) {
                    throw new WTypeError(func1.origin.originString() + " Incorrect number of parameters for " + func1.name() + " (expected 1, got " + func1.parameters().size() + ")");
                }
                break;
            case "operator==":
            case "operator<":
            case "operator<=":
            case "operator>":
            case "operator>=":
                if (func1.parameters().size() != Constants.WSL_2) {
                    throw new WTypeError(func1.origin.originString() + " Incorrect number of parameters for " + func1.name() + " (expected 2, got " + func1.parameters().size() + ")");
                }
                if (func1.returnType().equals(this._program.intrinsics.bool) == null) {
                    throw new WTypeError(func1.origin.originString() + " Return type of " + func1.name() + " must be bool but was " + func1.returnType().toString());
                }
                break;
            case "operator[]":
                checkGetter.apply("index");
                break;
            case "operator[]=":
                checkSetter.apply("index");
                break;
            case "operator&[]":
                checkAnder.apply("index");
                break;
            default:
                if (func1.name().startsWith("operator.")) {
                    if (func1.name().endsWith("=")) {
                        checkSetter.apply("dot");
                    } else {
                        checkGetter.apply("dot");
                    }
                    break;
                }
                if (func1.name().startsWith("operator&.")) {
                    checkAnder.apply("dot");
                    break;
                }
                throw new Error("Parser accepted unrecognized operator: ${func1.name}");
        }
    }

    public void visitFuncDef(Node node) {
        if (((FuncDef) node).shaderType() != null) {
            this.checkShaderType((FuncDef) node);
        }
        this.checkOperatorOverload(((FuncDef) node), (name) -> {
            return this._program.functions().get(name);
        });
        node.body().visit(this);
    }

    public void visitNativeFunc(Node node) {}

    public void visitProtocolDecl(Node node) {
        ProtocolDecl newNode = (ProtocolDecl) node;
        for (ProtocolFuncDecl signature : newNode.signatures()) {
            TypeVariableTracker typeVariableTracker = new TypeVariableTracker();
            for (HmType parameterType : signature.parameterTypes()) {
                parameterType.visit(typeVariableTracker);
            }
            Node.visit(signature.returnTypeForOverloadResolution(), typeVariableTracker);
            for (Node typeParameter : signature.typeParameters()) {
                if (!typeVariableTracker.set().contains(typeParameter)) {
                    throw new WTypeError(typeParameter.origin.originString(), "Type parameter to protocol signature not inferrable from value parameters");
                }
            }
            if (!typeVariableTracker.set().contains(newNode.typeVariable())) {
                throw new WTypeError(signature.origin.originString(), "Protocol`s type variable (" + newNode.name() + ") not mentioned in signature: " + signature.toString());
            }
            this.checkOperatorOverload(signature, (name) -> {
                if (newNode.signaturesByName(name) != null){
                    return newNode.signaturesByName(name).stream().map(data -> (Func) data).collect(Collectors.toList());
                }else{
                    return null;
                }
            });
        }
    }

    public void visitEnumType(Node node) {
        EnumType newNode = (EnumType) node;
        newNode.baseType().visit(this);

        HmType baseType = (HmType) newNode.baseType().unifyNode();

        if (!baseType.isInt()) {
            throw new WTypeError(newNode.origin.originString(), "Base type of enum is not an integer: " + newNode.baseType().toString());
        }

        for (EnumMember member : newNode.members()) {
            if (member.value == null) {
                continue;
            }
            HmType memberType = (HmType) member.value.visit(this);
            if (baseType.equalsWithCommit(memberType) == null) {
                throw new WTypeError(
                        member.origin.originString(),
                        "Type of enum member " +
                                member.value.name() +
                                " does not patch enum base type (member type is " + memberType.toString() + ", enum base type is " + newNode.baseType().toString() + ")"
                );
            }
        }

        int nextValue = ((NativeType) baseType).defaultValue;
        for (EnumMember member : newNode.members()) {
            if (member.value != null) {
                nextValue = ((NativeType) baseType).successorValue.apply((int) ((GenericLiteral) member.value.unifyNode()).valueForSelectedType());
                continue;
            }
            member.value = ((NativeType) baseType).createLiteral.apply(member.origin, nextValue);
            nextValue = ((NativeType) baseType).successorValue.apply(nextValue);
        }

        List<EnumMember> memberArray = newNode.members();


        for (int i = 0; i < memberArray.size(); ++i) {
            EnumMember member = memberArray.get(i);
            for (int j = i + 1; j < memberArray.size(); ++j) {
                EnumMember otherMember = memberArray.get(j);
                if (
                        ((NativeType) baseType).valuesEqual.apply((
                                (GenericLiteral) member.value.unifyNode()).valueForSelectedType(),
                                ((GenericLiteral) otherMember.value.unifyNode()).valueForSelectedType()
                        )
                ) {
                    throw new WTypeError(
                            otherMember.origin.originString() +
                                    "Duplicate enum member value (" + member.name() + " has " + member.value.toString() + " while " + otherMember.name() + " has " + otherMember.value.toString() + ")"
                    );
                }
            }
        }

        boolean foundZero = false;
        for (EnumMember member : newNode.members()) {
            if (((NativeType) baseType).valuesEqual.apply(((GenericLiteral) member.value.unifyNode()).valueForSelectedType(), (double) ((NativeType) baseType).defaultValue)) {
                foundZero = true;
                break;
            }
        }
        if (!foundZero) {
            throw new WTypeError(newNode.origin.originString(), "Enum does not have a member with the value zero");
        }
    }

    public void checkTypeArguments(LexerToken origin, List<Node> typeParameters, List<Node> typeArguments) {
        for (int i = 0; i < typeParameters.size(); ++i) {
            if (typeArguments.get(i) instanceof HmType) {
                HmType argumentIsType = (HmType) typeArguments.get(i);
                Object result = typeArguments.get(i).visit(this);
                if (argumentIsType != null) {
                    VerifyResult results = ((HmType) typeArguments.get(i)).inherits(((HmType) typeParameters.get(i)).protocol1());
                    if (!results.result) {
                        throw new WTypeError(origin.originString(), "Type argument does not inherit protocol: " + results.reason);
                    }
                } else {
                    if (((HmType) result).equalsWithCommit(typeParameters.get(i).hmType()) == null) {
                        throw new WTypeError(origin.originString(), "Wrong type for constexpr");
                    }
                }
            }
        }
    }

    public void visitTypeRef(Node node) {
        if (node.hmType() == null) {
            throw new Error("Type reference without a type in checker: " + node.toString() + " at " + node.origin.toString());
        }
        if (!(node.hmType() instanceof StructType)) {
            node.hmType().visit(this);
        }
        this.checkTypeArguments(node.origin, node.hmType().typeParameters, ((TypeRef) node).typeArguments);
    }

    public void visitArrayType(Node node) {
        ArrayType newNode = (ArrayType) node;
        newNode.elementType().visit(this);

        if (!newNode.numElements().isConstexpr()) {
            throw new WTypeError(newNode.origin.originString(), "Array length must be constexpr");
        }

        HmType type = (HmType) newNode.numElements().visit(this);

        if (type.equalsWithCommit(this._program.intrinsics.uint32) == null) {
            throw new WTypeError(newNode.origin.originString(), "Array length must be a uint32");
        }
    }

    public void visitVariableDecl2(Node node) {
        VariableDecl newNode = (VariableDecl) node;
        newNode.hmType().visit(this);
        if (newNode.initializer() != null) {
            HmType lhsType = newNode.hmType();
            HmType rhsType = (HmType) newNode.initializer().visit(this);
            if (lhsType.equalsWithCommit(rhsType) == null) {
                throw new WTypeError(newNode.origin.originString(), "Type mismatch in variable initialization: " + lhsType.toString() + " versus " + rhsType.toString());
            }
        }
    }

    public HmType visitAssignment2(Node node) {
        Assignment newNode = (Assignment) node;
        HmType lhsType = (HmType) newNode.lhs().visit(this);
        if (!newNode.lhs().isLValue()) {
            throw new WTypeError(newNode.origin.originString(), "LHS of assignment is not an LValue: " + newNode.lhs().toString() + newNode.lhs().notLValueReasonString());
        }
        HmType rhsType = (HmType) newNode.rhs().visit(this);
        if (lhsType.equalsWithCommit(rhsType) == null) {
            throw new WTypeError(newNode.origin.originString(), "Type mismatch in assignment: " + lhsType.toString() + " versus " + rhsType.toString());
        }
        newNode.hmType(lhsType);
        return lhsType;
    }

    public HmType visitIdentityExpression2(Node node) {
        return (HmType) ((Expression) node).target().visit(this);
    }

    public TypeRef visitReadModifyWriteExpression2(Node node) {
        ReadModifyWriteExpression newNode = (ReadModifyWriteExpression) node;
        TypeRef lhsType = (TypeRef) newNode.lValue().visit(this);
        if (!newNode.lValue().isLValue()) {
            throw new WTypeError(
                    newNode.origin.originString(),
                    "LHS of read-modify-write is not an LValue: " + newNode.lValue().toString() + newNode.lValue().notLValueReasonString()
            );
        }
        newNode.oldValueVar.hmType(lhsType);
        newNode.newValueVar.hmType(lhsType);
        newNode.oldValueVar.visit(this);
        newNode.newValueVar.visit(this);
        TypeRef newValueType = (TypeRef) newNode.newValueExp.visit(this);
        if (lhsType.equalsWithCommit(newValueType) == null) {
            throw new WTypeError(newNode.origin.originString(), "Type mismatch in read-modify-write: " + lhsType.toString() + " versus " + newValueType.toString());
        }
        return (TypeRef) newNode.resultExp.visit(this);
    }

    public void visitAnonymousVariable2(Node node) {
        if (((AnonymousVariable) node).hmType() == null) {
            throw new Error("Anonymous variable must know type before first appearance");
        }
    }

    public HmType visitDereferenceExpression2(Node node) {
        DereferenceExpression newNode = (DereferenceExpression) node;
        HmType type = (HmType) ((HmType) newNode.ptr().visit(this)).unifyNode();
        if (!type.isPtr()) {
            throw new WTypeError(newNode.origin.originString(), "Type passed to dereference is not a pointer: " + type.toString());
        }
        newNode.hmType(type.elementType());
        newNode.addressSpace(type.addressSpace());
        if (newNode.addressSpace() == null) {
            throw new Error("Null address space in type: " + type.toString());
        }
        return newNode.hmType();
    }

    public PtrType visitMakePtrExpression2(Node node) {
        MakePtrExpression newNode = (MakePtrExpression) node;
        HmType elementType = (HmType) ((HmType) newNode.lValue().visit(this)).unifyNode();
        if (!newNode.lValue().isLValue()) {
            throw new WTypeError(
                    newNode.origin.originString(),
                    "Operand to & is not an LValue: " + newNode.lValue().toString() + newNode.lValue().notLValueReasonString()
            );
        }
        return new PtrType(newNode.origin, ((Expression) newNode.lValue()).addressSpace(), elementType);
    }

    public ArrayRefType visitMakeArrayRefExpression2(Node node) {
        MakeArrayRefExpression newNode = (MakeArrayRefExpression) node;
        HmType elementType = (HmType) ((HmType) newNode.lValue().visit(this)).unifyNode();
        if (elementType.isPtr()) {
            newNode.become(new ConvertPtrToArrayRefExpression(newNode.origin, (VariableRef) newNode.lValue()));
            return new ArrayRefType(newNode.origin, elementType.addressSpace(), elementType.elementType());
        }

        if (!newNode.lValue().isLValue()) {
            throw new WTypeError(
                    newNode.origin.originString(),
                    "Operand to @ is not an LValue:" + newNode.lValue().toString() + newNode.lValue().notLValueReasonString()
            );
        }

        if (elementType.isArray()) {
            newNode.numElements = elementType.numElements();
            elementType = elementType.elementType();
        } else {
            newNode.numElements = UintLiteral.withType(newNode.origin, 1, this._program.intrinsics.uint32);
        }
        return new ArrayRefType(newNode.origin, ((Expression) newNode.lValue()).addressSpace(), elementType);
    }

    public void visitConvertToArrayRefExpression(Node node) {
        throw new Error("Should not exist yet.");
    }

    public HmType finishVisitingPropertyAccess(PropertyAccessExpression node, HmType baseType, List<Expression> extraArgs, List<HmType> extraArgTypes) {
        baseType = (HmType) baseType.visit(new AutoWrapper());
        node.baseType = baseType;

        // Such a type must exist. This may throw if it doesn't.
        ReferenceType typeForAnd = (ReferenceType) baseType.argumentTypeForAndOverload(node.origin);
        if (typeForAnd == null) {
            throw new Error("Cannot get typeForAnd");
        }
        WTypeError errorForGet = new WTypeError("", "");
        WTypeError errorForAnd = new WTypeError("", "");

        try {
            List<Expression> tmpExtraArgs = new ArrayList<>(List.of(node.base));
            extraArgs.forEach((item) -> {
                tmpExtraArgs.add(item);
            });

            List<HmType> tmpExtraArgTypes = new ArrayList<>(List.of(baseType));
            extraArgTypes.forEach((item) -> {
                tmpExtraArgTypes.add(item);
            });

            CallResolveResult result = CallExpression.resolve(
                    node.origin,
                    node.possibleGetOverloads,
                    ((FuncDef) this._currentStatement).typeParameters(),
                    node.getFuncName(),
                    new ArrayList<>(),
                    tmpExtraArgs,
                    tmpExtraArgTypes,
                    null
            );
            node.callForGet = result.call;
            node.resultTypeForGet = result.resultType;
        } catch (RuntimeException e) {
            if (!(e instanceof WTypeError)) {
                throw new RuntimeException(e);
            }
            errorForGet = (WTypeError) e;
        }

        try {
            Expression baseForAnd = (Expression) baseType.argumentForAndOverload(node.origin, node.base);

            List<Expression> tmpExtraArgs = new ArrayList<>(List.of(baseForAnd));
            extraArgs.forEach((item) -> {
                tmpExtraArgs.add(item);
            });

            List<HmType> tmpExtraArgTypes = new ArrayList<>(List.of(typeForAnd));
            extraArgTypes.forEach((item) -> {
                tmpExtraArgTypes.add(item);
            });

            CallResolveResult result = CallExpression.resolve(
                    node.origin,
                    node.possibleAndOverloads,
                    ((FuncDef) this._currentStatement).typeParameters(),
                    node.andFuncName(),
                    new ArrayList<>(),
                    tmpExtraArgs,
                    tmpExtraArgTypes,
                    null
            );
            node.callForAnd = result.call;
            node.resultTypeForAnd = ((PtrType) result.resultType.unifyNode()).returnTypeFromAndOverload(node.origin);
        } catch (RuntimeException e) {
            if (!(e instanceof WTypeError)) {
                throw new RuntimeException(e);
            }
            errorForAnd = (WTypeError) e;
        }

        if (node.resultTypeForGet == null && node.resultTypeForAnd == null) {
            throw new WTypeError(
                    node.origin.originString(),
                    "Cannot resolve access; tried by-value:\n" + errorForGet.typeErrorMessage + "\nand tried by-pointer:\n" + errorForAnd.typeErrorMessage
            );
        }

        if (node.resultTypeForGet != null && node.resultTypeForAnd != null && node.resultTypeForGet.equals(node.resultTypeForAnd) == null) {
            throw new WTypeError(
                    node.origin.originString(),
                    "Result type resolved by-value (" + node.resultTypeForGet.toString() + ") does not match result type resolved by-pointer (" + node.resultTypeForAnd.toString() + ")"
            );
        }

        try {
            List<Expression> tmpExtraArgs = new ArrayList<>(List.of(node.base));
            extraArgs.forEach((item) -> {
                tmpExtraArgs.add(item);
            });

            List<HmType> tmpExtraArgTypes = new ArrayList<>(List.of(baseType));
            extraArgTypes.forEach((item) -> {
                tmpExtraArgTypes.add(item);
            });
            tmpExtraArgTypes.add(node.resultType());

            CallResolveResult result = CallExpression.resolve(
                    node.origin,
                    node.possibleSetOverloads,
                    ((FuncDef) this._currentStatement).typeParameters(),
                    node.setFuncName(),
                    new ArrayList<>(),
                    tmpExtraArgs,
                    tmpExtraArgTypes,
                    null
            );
            node.callForSet = result.call;
            if (result.resultType.equals(baseType) == null) {
                throw new WTypeError(node.origin.originString(), "Result type of setter " + result.call.func1.toString() + " is not the base type " + baseType.toString());
            }
        } catch (RuntimeException e) {
            if (!(e instanceof WTypeError)) {
                throw new RuntimeException(e);
            }
            node.errorForSet = (WTypeError) e;
        }

        // OK, now we need to determine if we are an lvalue. We are an lvalue if we can be assigned to. We can
        // be assigned to if we have an ander or setter. But it's weirder than that. We also need the base to be
        // an lvalue, except unless the base is an array reference.
        if (node.callForAnd == null && node.callForSet == null) {
            node.isLValue(false);
            node.notLValueReason("Have neither ander nor setter. Tried setter:\n" + node.errorForSet.typeErrorMessage + "\nand tried ander:\n" +
                    errorForAnd.typeErrorMessage)
            ;
        } else if (!node.base.isLValue() && !baseType.isArrayRef()) {
            node.isLValue(false);
            node.notLValueReason("Base of property access is neither a lvalue nor an array reference");
        } else {
            node.isLValue(true);
            node.addressSpace(node.base.isLValue() ? node.base.addressSpace() : baseType.addressSpace());
        }

        return node.resultType();
    }

    public HmType visitDotExpression2(Node node) {
        DotExpression newNode = (DotExpression) node;
        HmType structType = (HmType) ((HmType) newNode.struct1().visit(this)).unifyNode();
        return this.finishVisitingPropertyAccess(newNode, structType, new ArrayList<>(), new ArrayList<>());
    }

    public HmType visitIndexExpression2(Node node) {
        IndexExpression newNode = (IndexExpression) node;
        HmType arrayType = (HmType) ((HmType) newNode.array().visit(this)).unifyNode();
        HmType indexType = (HmType) newNode.index.visit(this);
        return this.finishVisitingPropertyAccess(newNode, arrayType, new ArrayList<>(List.of(newNode.index)), new ArrayList<>(List.of(indexType)));
    }

    public HmType visitVariableRef2(Node node) {
        VariableRef newNode = (VariableRef) node;
        if (newNode.variable.hmType() == null) {
            throw new Error("Variable has no type: " + newNode.variable.toString());
        }
        return newNode.variable.hmType();
    }

    public void visitReturn(Node node) {
        Return newNode = (Return) node;
        if (newNode.value() != null) {
            HmType resultType = (HmType) newNode.value().visit(this);
            if (resultType == null) {
                throw new Error("Null result type from " + newNode.value().toString());
            }

            if (newNode.func1.returnType().equalsWithCommit((Node) resultType) == null) {
                throw new WTypeError(
                        newNode.origin.originString(),
                        "Trying to return " + resultType.toString() + " in a function that returns " + newNode.func1.returnType().toString()
                );
            }
            return;
        }

        if (newNode.func1.returnType().equalsWithCommit(this._program.intrinsics.hmVoid) == null) {
            throw new WTypeError(newNode.origin.originString(), "Non-void function must return a value");
        }
    }

    public HmType visitGenericLiteral2(Node node) {
        return ((GenericLiteral) node).hmType();
    }

    public NullType visitNullLiteral2(Node node) {
        return (NullType) ((NullLiteral) node).hmType();
    }

    public NativeType visitBoolLiteral2(Node node) {
        return this._program.intrinsics.bool;
    }

    public EnumType visitEnumLiteral2(Node node) {
        return ((EnumLiteral) node).member().enumType;
    }

    public void requireBool(CallExpression expression) {
        TypeRef type = (TypeRef) expression.visit(this);
        if (type == null) {
            throw new Error("Expression has no type, but should be bool: " + expression.toString());
        }

        if (type.equals(this._program.intrinsics.bool) == null) {
            throw new WTypeError("Expression isn't a bool: " + expression.toString(), "");
        }
    }

    public NativeType visitLogicalNot2(Node node) {
        this.requireBool((CallExpression) ((LogicalNot) node).operand());
        return this._program.intrinsics.bool;
    }

    public NativeType visitLogicalExpression2(Node node) {
        LogicalExpression newNode = (LogicalExpression) node;
        this.requireBool((CallExpression) newNode.left());
        this.requireBool((CallExpression) newNode.right());
        return this._program.intrinsics.bool;
    }

    public void visitIfStatement(Node node) {
        IfStatement newNode = (IfStatement) node;
        this.requireBool((CallExpression) newNode.conditional());
        newNode.body().visit(this);
        if (newNode.elseBody() != null) {
            newNode.elseBody().visit(this);
        }
    }

    public void visitWhileLoop(Node node) {
        WhileLoop newNode = (WhileLoop) node;
        this.requireBool((CallExpression) newNode.conditional());
        newNode.body().visit(this);
    }

    public void visitDoWhileLoop(Node node) {
        DoWhileLoop newNode = (DoWhileLoop) node;
        newNode.body().visit(this);
        this.requireBool((CallExpression) newNode.conditional());
    }

    public void visitForLoop(Node node) {
        ForLoop newNode = (ForLoop) node;
        if (newNode.initialization() != null) {
            newNode.initialization().visit(this);
        }
        if (newNode.condition() != null) {
            this.requireBool((CallExpression) newNode.condition());
        }
        if (newNode.increment() != null) {
            newNode.increment().visit(this);
        }
        newNode.body().visit(this);
    }

    public void visitSwitchStatement(Node node) {
        SwitchStatement newNode = (SwitchStatement) node;
        TypeRef type = ((TypeRef) newNode.value().visit(this)).commit();

        if (!((HmType) type.unifyNode()).isInt() && !(type.unifyNode() instanceof EnumType)) {
            throw new WTypeError(newNode.origin.originString(), "Cannot switch on non-integer/non-enum type: " + type.toString());
        }
        newNode.hmType(type);

        boolean hasDefault = false;

        for (SwitchCase switchCase : newNode.switchCases()) {
            switchCase.body().visit(this);

            if (switchCase.isDefault()) {
                hasDefault = true;
                continue;
            }

            if (!switchCase.value().isConstexpr()) {
                throw new WTypeError(switchCase.origin.originString(), "Switch case not constexpr: " + switchCase.value().toString());
            }

            HmType caseType = (HmType) switchCase.value().visit(this);
            if (type.equalsWithCommit(caseType) == null) {
                throw new WTypeError(
                        switchCase.origin.originString(),
                        "Switch case type does not match switch value type (case type is " + caseType.toString() + " but switch value type is " + type.toString()
                );
            }
        }
        for (int i = 0; i < newNode.switchCases().size(); ++i) {
            SwitchCase firstCase = newNode.switchCases().get(i);
            for (int j = i + 1; j < newNode.switchCases().size(); ++j) {
                SwitchCase secondCase = newNode.switchCases().get(j);
                if (firstCase.isDefault() != secondCase.isDefault()) {
                    continue;
                }
                if (firstCase.isDefault()) {
                    throw new WTypeError(secondCase.origin.originString(), "Duplicate default case in switch statement");
                }

                double a = ((Expression) firstCase.value().unifyNode()).valueForSelectedType();
                double b = ((Expression) secondCase.value().unifyNode()).valueForSelectedType();

                boolean valuesEqual = false;
                if (type.unifyNode() instanceof EnumType) {
                    valuesEqual = ((EnumType) type.unifyNode()).valuesEqual(a, b);
                }
                if (type.unifyNode() instanceof NativeType) {
                    valuesEqual = ((NativeType) type.unifyNode()).valuesEqual.apply(a, b);
                }

                if (valuesEqual) {
                    throw new WTypeError(
                            secondCase.origin.originString(),
                            "Duplicate case in switch statement for value " + ((Expression) firstCase.value().unifyNode()).valueForSelectedType()
                    );
                }
            }
        }

        if (!hasDefault) {
            Set<Double> includedValues = new HashSet<>();
            for (SwitchCase switchCase : newNode.switchCases()) {
                includedValues.add(((Expression) switchCase.value().unifyNode()).valueForSelectedType());
            }

            for (ResultMemberInfo member : ((HmType) type.unifyNode()).allValues()) {
                if (!includedValues.contains((double) member.value)) {
                    throw new WTypeError(newNode.origin.originString(), "Value not handled by switch statement: " + member.name);
                }
            }
        }
    }

    @Override
    public Object visitCommaExpression(Node node) {
        Expression result = null;
        for (HmValue expression : ((CommaExpression) node).list()) {
            result = (Expression) expression.visit(this);
        }
        return result;
    }

    public HmType visitCallExpression2(Node node) {
        CallExpression newNode = (CallExpression) node;
        List<HmType> typeArguments = newNode.typeArguments().stream().map(typeArgument -> (HmType) typeArgument.visit(this)).collect(Collectors.toList());
        List<HmType> argumentTypes = newNode.argumentList().stream().map(argument -> {
            HmType newArgument = (HmType) argument.visit(this);
            if (newArgument == null) {
                throw new Error("visitor returned null for " + argument.toString());
            }
            return (HmType) newArgument.visit(new AutoWrapper());
        }).collect(Collectors.toList());

        newNode.argumentTypes = argumentTypes;
        if (newNode.returnType() != null) {
            newNode.returnType().visit(this);
        }
        HmType result = newNode.resolve(
                newNode.possibleOverloads,
                ((FuncDef)this._currentStatement).typeParameters(),
                typeArguments);
        return result;
        
    }

    @Override
    public Map<String, Function<Base, Object>> mapReturn() {
        Map<String, Function<Base, Object>> methods = super.mapReturn();
        methods.put("visitCallExpression", base -> this.visitCallExpression2((Node) base));
        methods.put("visitLogicalExpression", base -> this.visitLogicalExpression2((Node) base));
        methods.put("visitLogicalNot", base -> this.visitLogicalNot2((Node) base));
        methods.put("visitEnumLiteral", base -> this.visitEnumLiteral2((Node) base));
        methods.put("visitBoolLiteral", base -> this.visitBoolLiteral2((Node) base));
        methods.put("visitNullLiteral", base -> this.visitNullLiteral2((Node) base));
        methods.put("visitGenericLiteral", base -> this.visitGenericLiteral2((Node) base));
        methods.put("visitVariableRef", base -> this.visitVariableRef2((Node) base));
        methods.put("visitIndexExpression", base -> this.visitIndexExpression2((Node) base));
        methods.put("visitDotExpression", base -> this.visitDotExpression2((Node) base));
        methods.put("visitConvertToArrayRefExpression", base -> {
            this.visitConvertToArrayRefExpression((Node) base);
            return null;
        });
        methods.put("visitMakeArrayRefExpression", base -> this.visitMakeArrayRefExpression2((Node) base));
        methods.put("visitMakePtrExpression", base -> this.visitMakePtrExpression2((Node) base));
        methods.put("visitDereferenceExpression", base -> this.visitDereferenceExpression2((Node) base));
        methods.put("visitReadModifyWriteExpression", base -> this.visitReadModifyWriteExpression2((Node) base));
        methods.put("visitIdentityExpression", base -> this.visitIdentityExpression2((Node) base));
        methods.put("visitAssignment", base -> this.visitAssignment2((Node) base));
        methods.put("visitVariableDecl", base -> {
            this.visitVariableDecl2((Node) base);
            return null;
        });
        return methods;
    }
}

class CommaExpression extends Expression {
    private List<HmValue> _list;

    CommaExpression(LexerToken origin, List<HmValue> list) {
        super(origin);
        this._list = list;
        for (HmValue expression : list) {
            if (expression == null) {
                throw new Error("null expression");
            }
        }
    }

    public List<HmValue> list() {
        return this._list;
    }

    // NOTE: It's super tempting to say that CommaExpression is an lValue if its last entry is an lValue. But,
    // PropertyResolver relies on this not being the case.
    @Override
    public String toString() {
        return "(" + this.list().stream().map(HmValue::toString).collect(Collectors.joining(",")) + ")";
    }
}

class ConstexprFolder extends Visitor {
    public void visitCallExpression(Node node) {
        super.visitCallExpression(node);

        if ("operator-".equals(node.name()) &&
                ((CallExpression) node).typeArguments().size() == 0 &&
                ((CallExpression) node).argumentList().size() == 1 &&
                ((GenericLiteral) ((CallExpression) node).argumentList().get(0).unifyNode()).isConstexpr() &&
                ((GenericLiteral) ((CallExpression) node).argumentList().get(0).unifyNode()).negConstexpr() != null) {
            ((CallExpression) node).become(((GenericLiteral) ((CallExpression) node).argumentList().get(0).unifyNode()).negConstexpr());
            return;
        }
    }

    public void visitTypeOrVariableRef(Node node) {}
}
// ConstexprTypeParameter class
class ConstexprTypeParameter extends HmValue {
    ConstexprTypeParameter(LexerToken origin, String name, TypeRef type) {
        this.origin = origin;
        this.name(name);
        this.hmType(type);
    }

    @Override
    public boolean isConstexpr() {
        return true;
    }

    @Override
    public boolean isUnifiable() {
        return true;
    }

    public boolean varIsLValue() {
        return false;
    }

    @Override
    public boolean typeVariableUnify(UnificationContext unificationContext, Node other) {
        if (!(other instanceof HmValue)) {
            return false;
        }
        if (!this.hmType().unify(unificationContext, other.hmType() == null ? new Node() : other.hmType())) {
            return false;
        }
        return this.typeVariableUnifyImpl(unificationContext, other);
    }

    @Override
    public boolean unifyImpl(UnificationContext unificationContext, Node other) {
        return this.typeVariableUnify(unificationContext, (HmValue) other);
    }

    @Override
    public VerifyResult verifyAsArgument(UnificationContext unificationContext) {
        return new VerifyResult(true);
    }

    @Override
    public VerifyResult verifyAsParameter(UnificationContext unificationContext) {
        return new VerifyResult(true);
    }

    @Override
    public String toString() {
        return this.hmType().name() + " " + this.name();
    }
}

class Continue extends Node {
    Continue(LexerToken origin) {
        super();
        this.origin = origin;
    }

    @Override
    public String toString() {
        return "Continue";
    }
}

class ConvertPtrToArrayRefExpression extends Expression {
    ConvertPtrToArrayRefExpression(LexerToken origin, VariableRef lValue) {
        super(origin);
        this.lValue(lValue);
    }

    @Override
    public String toString() {
        return "@(" + this.lValue().toString() + ")";
    }
}

class DereferenceExpression extends Expression {
    private HmValue _ptr;

    DereferenceExpression(LexerToken origin, HmValue ptr, HmType type, String addressSpace) {
        super(origin);
        this._ptr = ptr;
        this.hmType(type);
        this.addressSpace(addressSpace);
    }

    public HmValue ptr() {
        return this._ptr;
    }

    @Override
    public boolean isLValue() {
        if (this.isBecome) {
            return this.target().isLValue();
        } else {
            return true;
        }
    }

    @Override
    public String toString() {
        if (this.isBecome) {
            return "(" + this.target().toString() + ")";
        } else {
            return "*(" + this.ptr().toString() + ")";
        }
    }
}

class DoWhileLoop extends Node {
    private HmValue _conditional;

    DoWhileLoop(LexerToken origin, Block body, HmValue conditional) {
        super();
        this.origin = origin;
        this._conditional = conditional;
        this.body(body);
    }

    public HmValue conditional() {
        return this._conditional;
    }

    @Override
    public String toString() {
        return "do " + this.body().toString() + " while (" + this.conditional().toString() + ");";
    }
}

class DotExpression extends PropertyAccessExpression {
    String fieldName = "";

    DotExpression(LexerToken origin, Expression struct1, String fieldName) {
        super(origin, struct1);
        this.fieldName = fieldName;
    }

    @Override
    public Node struct1() {
        if (this.isBecome) {
            return super.struct1();
        } else {
            return this.base;
        }
    }

    @Override
    public String getFuncName() {
        if (this.isBecome) {
            return super.getFuncName();
        } else {
            return "operator." + this.fieldName;
        }
    }

    @Override
    public String andFuncName() {
        if (this.isBecome) {
            return super.andFuncName();
        } else {
            return "operator&." + this.fieldName;
        }
    }

    @Override
    public String setFuncName() {
        if (this.isBecome) {
            return super.setFuncName();
        } else {
            return "operator." + this.fieldName + "=";
        }
    }

    @Override
    public String toString() {
        if (this.isBecome) {
            return "(" + this.target().toString() + ")";
        } else {
            return "(" + this.struct1().toString() + "." + this.fieldName + ")";
        }
    }
}

class EArrayRef {
    private EPtr _ptr;
    private int _length;

    EArrayRef(EPtr ptr, int length) {
        this._ptr = ptr;
        this._length = length;
    }

    public EPtr ptr() {
        return this._ptr;
    }

    public int length() {
        return this._length;
    }

    @Override
    public String toString() {
        return "A:<" + this.ptr().toString() + ", " + this.length() + ">";
    }
}

class EBuffer {
    private int _index;
    private List<Object> _array;

    EBuffer(int size) {
        if (!Constants.canAllocateEBuffers) {
            throw new Error("Trying to allocate EBuffer while allocation is disallowed");
        }
        this._index = Constants.eBufferCount + 1;
        this._array = new ArrayList<>(Collections.nCopies(size,null));
    }

    public static EPtr setCanAllocateEBuffers(boolean value, Supplier<EPtr> callback) {
        boolean oldCanAllocateEBuffers = Constants.canAllocateEBuffers;
        Constants.canAllocateEBuffers = value;
        try {
            return callback.get();
        } finally {
            Constants.canAllocateEBuffers = oldCanAllocateEBuffers;
        }
    }

    public static EPtr disallowAllocation(Supplier<EPtr> callback) {
        return EBuffer.setCanAllocateEBuffers(false, callback);
    }

    public static EPtr allowAllocation(Supplier<EPtr> callback) {
        return EBuffer.setCanAllocateEBuffers(true, callback);
    }

    public Object eBufferGet(int index) {
        if (index < 0 || index >= this._array.size()) {
            throw new Error("Out of bounds buffer access (buffer = " + this.toString() + ", index = " + index + ")");
        }
        return this._array.get(index);
    }

    public void eBufferSet(int index, Object value) {
        if (index < 0 || index >= this._array.size()) {
            throw new Error("Out of bounds buffer access (buffer = " + this + ", index = " + index + ")");
        }
        this._array.set(index, value);
    }

    public int index() {
        return this._index;
    }

    @Override
    public String toString() {
        return "B" + this._index + ":[" + this._array + "]";
    }
}

class EBufferBuilder extends Visitor {
    Program _program;

    EBufferBuilder(Program program) {
        super();
        this._program = program;
    }

    public EPtr createEPtr(HmType type) {
        if (type.size() == -1) {
            throw new Error("Type does not have size: " + type);
        }
        EBuffer buffer = new EBuffer(type.size());
        type.populateDefaultValue(buffer, 0);
        return new EPtr(buffer, 0);
    }

    public void createEPtrForNode(HmValue node) {
        if (node.hmType() == null) {
            throw new Error("node has no type: " + node);
        }
        node.ePtr(this.createEPtr(node.hmType()));
    }

    public void visitFuncParameter(Node node) {
        this.createEPtrForNode((HmValue) node);
    }

    public void visitVariableDecl(Node node) {
        VariableDecl newNode = (VariableDecl) node;
        this.createEPtrForNode(newNode);
        if (newNode.initializer() != null) {
            newNode.initializer().visit(this);
        }
    }

    public void visitFuncDef(Node node) {
        node.returnEPtr = this.createEPtr(((FuncDef) node).returnType());
        super.visitFuncDef(node);
    }

    public void visitCallExpression(Node node) {
        node.resultEPtr = this.createEPtr((HmType) ((CallExpression) node).resultType);
        super.visitCallExpression(node);
    }

    public void visitMakePtrExpression(Node node) {
       node.ePtr(EPtr.boxStatic(null));
        super.visitMakePtrExpression(node);
    }

    public void visitGenericLiteral(Node node) {
        node.ePtr(EPtr.boxStatic());
    }

    public void visitNullLiteral(Node node) {
        node.ePtr(EPtr.boxStatic());
    }

    public void visitBoolLiteral(Node node) {
        node.ePtr(EPtr.boxStatic());
    }

    public void visitEnumLiteral(Node node) {
        node.ePtr(EPtr.boxStatic());
    }

    public void visitLogicalNot(Node node) {
        node.ePtr(EPtr.boxStatic());
        super.visitLogicalNot((LogicalNot) node);
    }

    public void visitLogicalExpression(Node node) {
        node.ePtr(EPtr.boxStatic());
        super.visitLogicalExpression((LogicalExpression) node);
    }

    public void visitAnonymousVariable(Node node) {
        this.createEPtrForNode((HmValue) node);
    }

    public void visitMakeArrayRefExpression(Node node) {
        MakeArrayRefExpression newNode = (MakeArrayRefExpression) node;
        node.ePtr(EPtr.boxStatic());
        super.visitMakeArrayRefExpression(newNode);
    }

    public void visitConvertPtrToArrayRefExpression(Node node) {
        ConvertPtrToArrayRefExpression newNode = (ConvertPtrToArrayRefExpression) node;
        node.ePtr(EPtr.boxStatic());
        super.visitConvertPtrToArrayRefExpression(newNode);
    }
}

class EPtr extends Node {
    private EBuffer _buffer;
    private int _offset;

    EPtr(EBuffer buffer, int offset) {
        super();
        if (offset == -1 || offset != offset) {
            throw new Error("Bad offset: " + offset);
        }
        this._buffer = buffer;
        this._offset = offset;
    }

    public static EPtr boxStatic(Object value) {
        return new EPtr(new EBuffer(1), 0).box(value);
    }
    public static EPtr boxStatic() {
        return EPtr.boxStatic(null);
    }
    public EPtr box(Object value) {
        this._buffer.eBufferSet(0, value);
        return this;
    }

    public EBuffer buffer() {
        return this._buffer;
    }

    public int offset() {
        return this._offset;
    }

    public EPtr plus(int offset) {
        return new EPtr(this.buffer(), this.offset() + offset);
    }

    public Object loadValue() {
        return this._buffer.eBufferGet(this.offset());
    }

    public Object ePtrGet(int offset) {
        return this._buffer.eBufferGet(this.offset() + offset);
    }


    public void ePtrSet(int offset, Object value) {
        this._buffer.eBufferSet(this.offset() + offset, value);
    }

    public void copyFrom(EPtr other, int size) {
        if (size == -1) {
            throw new Error("Cannot copy null size");
        }
        int i = size;
        while (i > 0) {
            i -= 1;
            this.ePtrSet(i, (Object) other.ePtrGet(i));
        }
    }

    @Override
    public String toString() {
        return "B" + this.buffer().index() + ":" + this.offset();
    }
}

class EnumLiteral extends Expression {
    private EnumMember _member = null;

    EnumLiteral(LexerToken origin, EnumMember member) {
        super(origin);
        this._member = member;
    }

    public EnumMember member() {
        return this._member;
    }

    @Override
    public HmType hmType() {
        return this.member().enumType;
    }

    @Override
    public void hmType(HmType newValue) {
        this.member().enumType = (EnumType) newValue;
    }

    @Override
    public boolean isConstexpr() {
        return true;
    }

    @Override
    public double valueForSelectedType() {
        return ((Expression) this.member().value.unifyNode()).valueForSelectedType();
    }

    @Override
    public boolean unifyImpl(UnificationContext unificationContext, Node other) {
        if (!(other instanceof EnumLiteral)) {
            return false;
        }
        return this.member() == ((EnumLiteral) other).member();
    }

    @Override
    public String toString() {
        return this.member().enumType.name() + "." + this.member().name();
    }
}

class EnumMember extends Node {
    public Expression value;
    public EnumType enumType = null;

    EnumMember(LexerToken origin, String name, Expression value) {
        super();
        this.origin = origin;
        this.value = value;
        this.name(name);
    }

    @Override
    public String toString() {
        String result = this.name();
        if (this.value != null) {
            result += " = " + this.value.toString();
        }
        return result;
    }
}

class EnumType extends HmType {
    private HmType _baseType;
    private List<Map<String, EnumMember>> _members = new ArrayList<Map<String, EnumMember>>();
    EnumType(LexerToken origin, String name, HmType baseType) {
        super();
        this.origin = origin;
        this._baseType = baseType;
        this._members = new ArrayList<>();
        this.name(name);
    }

    public void add(EnumMember member) {
        ArrayList<String> names = new ArrayList<>();
        for (Map<String, EnumMember> ele : this._members) {
            names.add((String) ele.keySet().toArray()[0]);
        }

        if (names.contains(member.name())) {
            throw new WTypeError("Type error at " + member.origin.originString() + ":Duplicate enum member name:" + member.name());
        }

        member.enumType = this;
        Map<String, EnumMember> dic = new HashMap<>();
        dic.put(member.name(), member);
        this._members.add(dic);
    }

    public HmType baseType() {
        return this._baseType;
    }

    public EnumMember memberByName(String name) {
        for (Map<String, EnumMember> ele : this._members) {
            if (((String) ele.keySet().toArray()[0]).equals(name)) {
                return ele.get(name);
            }
        }
        return null;
    }

    public List<EnumMember> members() {
        List<EnumMember> tmp = new ArrayList<EnumMember>();
        for (Map<String, EnumMember> ele : this._members) {
            tmp.add((EnumMember) ele.values().toArray()[0]);
        }
        return tmp;
    }

    @Override
    public boolean isPrimitive() {
        return true;
    }

    public List<ResultMemberInfo> allValues() {
        List<ResultMemberInfo> arr = new ArrayList<>();
        for (EnumMember member : this.members()) {
            arr.add(new ResultMemberInfo(Double.valueOf(((GenericLiteral) member.value.unifyNode()).valueForSelectedType()).intValue(), member.name()));
        }
        return arr;
    }

    public boolean valuesEqual(double a, double b) {
        return ((NativeType) this.baseType().unifyNode()).valuesEqual.apply((double) a, (double) b);
    }

    @Override
    public TypeRef populateDefaultValue(EBuffer buffer, int offset) {
        this.baseType().populateDefaultValue(buffer, offset);
        return null;
    }

    @Override
    public int size() {
        return this.baseType().size();
    }

    @Override
    public String toString() {
        return "enum " + this.name() + " : " + this.baseType().toString() + " { " + this.members().stream().map(EnumMember::toString).collect(Collectors.joining(",")) + " }";
    }
}

class Evaluator extends Visitor {
    public Program _program;

    Evaluator(Program program) {
        super();
        this._program = program;
    }

    public EPtr snapshot(HmType type, EPtr dstPtr, EPtr srcPtr) {
        int size = type.size();
        if (size == -1) {
            throw new Error("Cannot get size of type: " + type.toString() + "(size = " + size + ", constructor = " + type.toString());
        }
        EPtr ptr = dstPtr;
        if (ptr == null) {
            ptr = new EPtr(new EBuffer(size), 0);
        }
        ptr.copyFrom(srcPtr, size);
        return ptr;
    }

    public EPtr runFunc(Func func1) {
        return EBuffer.disallowAllocation(() -> this.runBody(func1.returnType(), func1.returnEPtr, (Block) func1.body()));
    }

    public EPtr runBody(HmType type, EPtr ptr, Block block) {
        if (ptr == null) {
            throw new Error("Null ptr");
        }
        try {
            block.visit(this);
            // FIXME: We should have a check that there is no way to drop out of a function without
            // returning unless the function returns void.

            EPtr value = Constants.assignmentEptr;
            Constants.assignmentEptr = null;
            return value;
        } catch (RuntimeException e) {
            if (e instanceof NormalError) {
                NormalError error = (NormalError) e;
                if ((error != null && Constants.breakException.equals(error.message)) || Constants.continueException.equals(error.message)) {
                    throw new Error("Should not see break/continue at function scope");
                }

            }
            if (e instanceof ReturnException) {
                EPtr result = this.snapshot(type, ptr, ((ReturnException) e).value());
                return result;
            }
            throw e;
        }
    }

    public EPtr visitFunctionLikeBlock2(Node node) {
        FunctionLikeBlock newNode = (FunctionLikeBlock) node;
        for (int i = 0; i < newNode.argumentList().size(); ++i) {
            newNode.parameters().get(i).ePtr().copyFrom((EPtr) newNode.argumentList().get(i).visit(this), newNode.parameters().get(i).hmType().size());
        }
        EPtr result = this.runBody(newNode.returnType(), newNode.returnEPtr, (Block) newNode.body());
        return result;
    }

    public void visitReturn(Node node) {
        Return newNode = (Return) node;
        throw new ReturnException(newNode.value() != null ? ((EPtr) newNode.value().visit(this)) : null);
    }

    public void visitVariableDecl(Node node) {
        VariableDecl newNode = (VariableDecl) node;
        if (newNode.ePtr().buffer() == null) {
            throw new Error("eptr without buffer in " + newNode.toString());
        }
        newNode.hmType().populateDefaultValue(newNode.ePtr().buffer(), newNode.ePtr().offset());
        if (newNode.initializer() != null) {
            newNode.ePtr().copyFrom((EPtr) newNode.initializer().visit(this), newNode.hmType().size());
        }
    }

    public EPtr visitAssignment2(Node node) {
        Assignment newNode = (Assignment) node;
        EPtr target = (EPtr) newNode.lhs().visit(this);
        EPtr source = (EPtr) newNode.rhs().visit(this);
        target.copyFrom(source, newNode.hmType().size());
        Constants.assignmentEptr = target;
        return target;
    }

    public EPtr visitIdentityExpression2(Node node) {
        return (EPtr) node.target().visit(this);
    }

    public EPtr visitDereferenceExpression2(Node node) {
        DereferenceExpression newNode = (DereferenceExpression) node;
        EPtr ptr = (EPtr) ((EPtr) newNode.ptr().visit(this)).loadValue();
        if (ptr == null) {
            throw new WTrapError(newNode.origin.originString(), "Null dereference");
        }
        return ptr;
    }

    public EPtr visitMakePtrExpression2(Node node) {
        MakePtrExpression newNode = (MakePtrExpression) node;
        EPtr ptr = (EPtr) newNode.lValue().visit(this);
        return newNode.ePtr().box(ptr);
    }

    public EPtr visitMakeArrayRefExpression2(Node node) {
        MakeArrayRefExpression newNode = (MakeArrayRefExpression) node;
        return newNode.ePtr().box(new EArrayRef((EPtr) newNode.lValue().visit(this), WSLNumber.objectToInt(((EPtr) newNode.numElements.visit(this)).loadValue())));
    }

    public EPtr visitConvertPtrToArrayRefExpression2(Node node) {
        ConvertPtrToArrayRefExpression newNode = (ConvertPtrToArrayRefExpression) node;
        return newNode.ePtr().box(new EArrayRef((EPtr) ((EPtr) newNode.lValue().visit(this)).loadValue(), 1));
    }

    public EPtr visitCommaExpression2(Node node) {
        CommaExpression newNode = (CommaExpression) node;
        EPtr result = null;
        for (HmValue expression : newNode.list()) {
            result = (EPtr) expression.visit(this);
        }
        return result;
    }

    public EPtr visitVariableRef2(Node node) {
        return ((VariableRef) node).variable.ePtr();
    }

    public EPtr visitGenericLiteral2(Node node) {
        GenericLiteral newNode = (GenericLiteral) node;
        EPtr eptr = newNode.ePtr().box(newNode.valueForSelectedType());
        Constants.assignmentEptr = eptr;
        return eptr;
    }

    public EPtr visitNullLiteral2(Node node) {
        return ((NullLiteral) node).ePtr().box(null);
    }

    public EPtr visitBoolLiteral2(Node node) {
        BoolLiteral newNode = (BoolLiteral) node;
        return newNode.ePtr().box(newNode.value());
    }

    public EPtr visitEnumLiteral2(Node node) {
        EnumLiteral newNode = (EnumLiteral) node;
        return newNode.ePtr().box(((Expression) newNode.member().value.unifyNode()).valueForSelectedType());
    }

    public EPtr visitLogicalNot2(Node node) {
        LogicalNot newNode = (LogicalNot) node;
        boolean result = !(boolean) ((EPtr) newNode.operand().visit(this)).loadValue();
        return newNode.ePtr().box(result);
    }

    public EPtr visitLogicalExpression2(Node node) {
        LogicalExpression newNode = (LogicalExpression) node;
        boolean lhs = (boolean) ((EPtr) newNode.left().visit(this)).loadValue();
        boolean rhs = (boolean) ((EPtr) newNode.right().visit(this)).loadValue();
        boolean result;
        switch (newNode.text()) {
            case "&&":
                result = lhs && rhs;
                break;
            case "||":
                result = lhs || rhs;
                break;
            default:
                throw new Error("Unknown type of logical expression");
        }
        return newNode.ePtr().box(result);
    }

    public EPtr visitIfStatement2(Node node) {
        IfStatement newNode = (IfStatement) node;
        if ((boolean) ((EPtr) newNode.conditional().visit(this)).loadValue()) {
            return (EPtr) newNode.body().visit(this);
        } else if (newNode.elseBody() != null) {
            return (EPtr) newNode.elseBody().visit(this);
        } else {
            return null;
        }
    }

    public void visitWhileLoop(Node node) {
        WhileLoop newNode = (WhileLoop) node;
        while ((boolean) ((EPtr) newNode.conditional().visit(this)).loadValue()) {
            try {
                newNode.body().visit(this);
            } catch (RuntimeException e) {
                if (e instanceof NormalError) {
                    NormalError error = (NormalError) e;
                    if (Constants.breakException.equals(error.message)) {
                        break;
                    }
                    if (Constants.continueException.equals(error.message)) {
                        continue;
                    }
                }
                throw e;
            }
        }
    }

    public void visitDoWhileLoop(Node node) {
        DoWhileLoop newNode = (DoWhileLoop) node;

        do {
            try {
                newNode.body().visit(this);
            } catch (RuntimeException e) {
                NormalError error = (NormalError) e;
                if (Constants.breakException.equals(error.message)) {
                    break;
                }
                if (Constants.continueException.equals(error.message)) {
                    continue;
                }
                throw e;
            }
        } while ((boolean) ((EPtr) newNode.conditional().visit(this)).loadValue());
    }

    public void visitForLoop(Node node) {
        ForLoop newNode = (ForLoop) node;
        if (newNode.initialization() != null) newNode.initialization().visit(this);
        while (newNode.condition() != null ? ((boolean) ((EPtr) newNode.condition().visit(this)).loadValue()) : true) {
            try {
                newNode.body().visit(this);
            } catch (RuntimeException e) {
                if (e instanceof  NormalError){
                    NormalError error = (NormalError) e;
                    if (Constants.breakException.equals(error.message)) {
                        break;
                    }
                    if (Constants.continueException.equals(error.message)) {
                        if (newNode.increment() != null) newNode.increment().visit(this);
                        continue;
                    }
                }
                throw e;
            }
            if (newNode.increment() != null) newNode.increment().visit(this);
        }
    }

    public void visitSwitchStatement(Node node) {
        SwitchStatement newNode = (SwitchStatement) node;
        Function<Function<SwitchCase, Boolean>, Boolean> findAndRunCast = (predicate -> {
            for (int i = 0; i < newNode.switchCases().size(); ++i) {
                SwitchCase switchCase = newNode.switchCases().get(i);
                if (predicate.apply(switchCase)) {
                    try {
                        for (int j = i; j < newNode.switchCases().size(); ++j) {
                            newNode.switchCases().get(j).visit(this);
                        }
                    } catch (Throwable e) {
                        if (e instanceof NormalError) {
                            NormalError error = (NormalError) e;
                            if (error != null && !Constants.breakException.equals(error.message)) {
                                throw e;
                            }
                        }
                    }
                    return true;
                }
            }
            return false;
        });
        int value = WSLNumber.objectToInt( ((EPtr) newNode.value().visit(this)).loadValue());
        Boolean found = findAndRunCast.apply(switchCase -> {
            if (switchCase.isDefault()) {
                return false;
            }
            if (newNode.hmType().unifyNode() instanceof EnumType) {
                return ((EnumType) newNode.hmType().unifyNode()).valuesEqual(value, ((Expression) switchCase.value().unifyNode()).valueForSelectedType());
            } else {
                return ((NativeType) newNode.hmType().unifyNode()).valuesEqual.apply((double) value, (double) ((Expression) switchCase.value().unifyNode()).valueForSelectedType());
            }
        });
        if (found) {
            return;
        }
        found = findAndRunCast.apply(SwitchCase::isDefault);
        if (found == null) {
            throw new Error("Switch statement did not find case");
        }
    }

    public void visitBreak(Node node) {
        throw new NormalError(Constants.breakException);
    }

    public void visitContinue(Node node) {
        throw new NormalError(Constants.continueException);
    }

    public void visitTrapStatement(Node node) {
        throw new WTrapError(node.origin.originString(), "Trap statement");
    }

    public void visitAnonymousVariable(Node node) {
        node.hmType().populateDefaultValue(node.ePtr().buffer(), node.ePtr().offset());
    }

    public EPtr visitCallExpression2(Node node) {
        CallExpression newNode = (CallExpression) node;
        List<Supplier<EPtr>> callArguments = new ArrayList<>();
        for (int i = 0; i < newNode.argumentList().size(); ++i) {
            HmValue argument = newNode.argumentList().get(i);
            HmType type = newNode.nativeFuncInstance.parameterTypes().get(i);
            if (type == null || argument == null) {
                throw new Error("Cannot get type or argument; i = " + i + ", argument = " + argument.toString() + ", type = " + type.toString() + "; in " + newNode.toString());
            }
            EPtr argumentValue = (EPtr) argument.visit(this);
            if (argumentValue == null) {
                throw new Error("Null argument value, i = " + i + ", node = " + newNode.toString());
            }
            callArguments.add(new Supplier<EPtr>() {
                @Override
                public EPtr get() {
                    EPtr result = snapshot(type, null, argumentValue);
                    return result;
                }
            });
        }

        EPtr result = EBuffer.allowAllocation(new Supplier<EPtr>() {
            @Override
            public EPtr get() {
                return ((NativeFunc) newNode.func1).implementation.apply(
                        callArguments.stream().map(thunk -> thunk.get()).collect(Collectors.toList()),
                        newNode);            }
        });
        result = this.snapshot(newNode.nativeFuncInstance.returnType(), newNode.resultEPtr, result);

        return result;
    }

    @Override
    public Map<String, Function<Base, Object>> mapReturn() {
        Map<String, Function<Base, Object>> methods = super.mapReturn();
        methods.put(
                "visitCallExpression",
                base -> visitCallExpression2((Node) base));
        methods.put(
                "visitIfStatement",
                base -> visitIfStatement2((Node) base));
        methods.put(
                "visitLogicalExpression",
                base -> visitLogicalExpression2((Node) base));
        methods.put(
                "visitLogicalNot",
                base -> visitLogicalNot2((Node) base));
        methods.put(
                "visitEnumLiteral",
                base -> visitEnumLiteral2((Node) base));
        methods.put(
                "visitBoolLiteral",
                base -> visitBoolLiteral2((Node) base));
        methods.put(
                "visitNullLiteral",
                base -> visitNullLiteral2((Node) base));
        methods.put(
                "visitGenericLiteral",
                base -> visitGenericLiteral2((Node) base));
        methods.put(
                "visitVariableRef",
                base -> visitVariableRef2((Node) base));
        methods.put(
                "visitCommaExpression",
                base -> visitCommaExpression2((Node) base));
        methods.put(
                "visitConvertPtrToArrayRefExpression",
                base -> visitConvertPtrToArrayRefExpression2((Node) base));
        methods.put(
                "visitMakeArrayRefExpression",
                base -> visitMakeArrayRefExpression2((Node) base));
        methods.put(
                "visitMakePtrExpression",
                base -> visitMakePtrExpression2((Node) base));
        methods.put(
                "visitDereferenceExpression",
                base -> visitDereferenceExpression2((Node) base));
        methods.put(
                "visitIdentityExpression",
                base -> visitIdentityExpression2((Node) base));
        methods.put(
                "visitFunctionLikeBlock",
                base -> visitFunctionLikeBlock2((Node) base));
        methods.put(
                "visitAssignment",
                base -> visitAssignment2((Node) base));
        return methods;
    }
}

class ExpressionFinder extends Visitor {
    public Consumer<Node> callback;

    ExpressionFinder(Consumer<Node> callback) {
        super();
        this.callback = callback;
    }

    public void visitFunc(Node node) {
        Func newNode = (Func) node;
        this.callback.accept(newNode.returnType());
        for (Node typeParameter : newNode.typeParameters()) {
            typeParameter.visit(this);
        }
        for (FuncParameter parameter : newNode.parameters()) {
            parameter.visit(this);
        }
    }

    public void visitFuncParameter(Node node) {
        this.callback.accept(node.hmType());
    }

    public void visitConstexprTypeParameter(Node node) {
        this.callback.accept(node.hmType());
    }

    public void visitAssignment(Node node) {
        this.callback.accept(node);
    }

    public void visitReadModifyWriteExpression(Node node) {
        this.callback.accept((ReadModifyWriteExpression) node);
    }

    public void visitIdentityExpression(Node node) {
        this.callback.accept((Expression) node);
    }

    public void visitCallExpression(Node node) {
        this.callback.accept(node);
    }

    public void visitReturn(Node node) {
        Return newNode = (Return) node;
        if (newNode.value() != null) {
            this.callback.accept(newNode.value());
        }
    }

    public void visitWhileLoop(Node node) {
        WhileLoop newNode = (WhileLoop) node;
        this.callback.accept(newNode.conditional());
        newNode.body().visit(this);
    }

    public void visitDoWhileLoop(Node node) {
        DoWhileLoop newNode = (DoWhileLoop) node;
        newNode.body().visit(this);
        this.callback.accept(newNode.conditional());
    }

    public void visitIfStatement(Node node) {
        IfStatement newNode = (IfStatement) node;
        this.callback.accept(newNode.conditional());
        newNode.body().visit(this);
        if (newNode.elseBody() != null) {
            newNode.elseBody().visit(this);
        }
    }

    public void visitForLoop(Node node) {
        // Initialization is a statement, not an expression. If it's an assignment or variableDecl, we'll
        // catch it and redirect to the callback.
        ForLoop newNode = (ForLoop) node;
        if (newNode.initialization() != null) {
            newNode.initialization().visit(this);
        }
        if (newNode.condition() != null) {
            this.callback.accept(newNode.condition());
        }
        if (newNode.increment() != null) {
            this.callback.accept(newNode.increment());
        }
        newNode.body().visit(this);
    }

    public void visitVariableDecl(Node node) {
        VariableDecl newNode = (VariableDecl) node;
        this.callback.accept(newNode.hmType());
        if (newNode.initializer() != null) {
            this.callback.accept(newNode.initializer());
        }
    }
}

class Field extends Node {
    public int offset = -1;

    Field(LexerToken origin, String name, HmType type) {
        super();
        this.origin = origin;
        this.name(name);
        this.hmType(type);
    }

    @Override
    public String toString() {
        return this.hmType().toString() + " " + this.name();
    }
}

class ForLoop extends Node {
    ForLoop(LexerToken origin, Expression initialization, HmValue condition, Expression increment, Node body) {
        super();
        this.origin = origin;
        this.initialization(initialization);
        this.condition(condition);
        this.increment(increment);
        this.body(body);
    }

    @Override
    public String toString() {
        return "for (" + ((this.initialization() != null) ? this.initialization().toString() : " ") + "; " + ((this.condition() != null) ? this.condition().toString() : "") + "; " + ((this.increment() != null) ? this.increment().toString() : "") + ") " +
                this.body();
    }
}

class Func extends Node {
    public ProtocolDecl protocolDecl;
    public HmType _returnType = new HmType();
    public List<Node> _typeParameters = new ArrayList<>();
    public List<FuncParameter> _parameters = new ArrayList<>();
    private boolean _isCast = false;
    private String _shaderType;
    public boolean isRestricted = false;
    public boolean inlined = false;

    public HmType uninstantiatedReturnType = new HmType();

    Func(
            LexerToken origin,
            String name,
            HmType returnType,
            List<Node> typeParameters,
            List<FuncParameter> parameters,
            boolean isCast,
            String shaderType
    ) {
        super();
        for (FuncParameter parameter :
                parameters) {
            if (parameter == null) {
                throw new Error("Null parameter");
            }
            if (parameter.hmType() == null) {
                throw new Error("Null parameter type");
            }
        }
        this.origin = origin;
        this.protocolDecl = new ProtocolDecl(origin, name);
        this._returnType = returnType;
        this._typeParameters = typeParameters;
        this._parameters = parameters;
        this._isCast = isCast;
        this._shaderType = shaderType;
        this.uninstantiatedReturnType = returnType;
        this.name(name);
    }

    public HmType returnType() {
        return this._returnType;
    }

    public List<Node> typeParameters() {
        return this._typeParameters;
    }

    public void typeParameters(ArrayList<Node> newValue) {
        this._typeParameters = newValue;
    }

    public List<Node> typeParametersForCallResolution() {
        return this._typeParameters;
    }

    public List<FuncParameter> parameters() {
        return this._parameters;
    }

    public List<HmType> parameterTypes() {
        return this.parameters().stream().map(FuncParameter::hmType).collect(Collectors.toList());
    }

    public boolean isCast() {
        return this._isCast;
    }

    public String shaderType() {
        return this._shaderType;
    }

    public HmType returnTypeForOverloadResolution() {
        return this.isCast() ? this.returnType() : null;
    }

    @Override
    public String kind() {
        return "Func";
    }

    String toDeclString() {
        String result = "";
        if (this.shaderType() != null) {
            result += this.shaderType();
        }
        if (this.isCast()) {
            result += "operator<" + this.typeParameters().stream().map(Node::toString).collect(Collectors.joining(",")) + "> " + this.returnType().toString();
        } else {
            result += this.returnType().toString() + " " + this.name() + "<" + (this.typeParameters().size() == 0 ? "" : this.typeParameters().stream().map(Node::toString).collect(Collectors.joining(","))) + ">";
        }
        return result + "(" + this.parameters().stream().map(FuncParameter::toString).collect(Collectors.joining(",")) + ")";
    }

    @Override
    public String toString() {
        return this.toDeclString();
    }
}

class FuncDef extends Func {
    FuncDef(
            LexerToken origin,
            String name,
            HmType returnType,
            List<Node> typeParameters,
            List<FuncParameter> parameters,
            Block body,
            boolean isCast,
            String shaderType
    ) {
        super(origin, name, returnType, typeParameters, parameters, isCast, shaderType);
        this.body(body);
        this.isRestricted = false;
    }

    public void rewrite(Inliner rewriter) {
        if (this._typeParameters.size() > 0) {
            throw new Error("Cannot rewrite an uninstantiated function");
        }
        this._returnType = (HmType) this._returnType.visit(rewriter);
        this._parameters = this._parameters.stream().map(parameter -> {
            return (FuncParameter) parameter.visit(rewriter);
        }).collect(Collectors.toList());
        this.body((Block) this.body().visit(rewriter));
    }

    @Override
    public String toString() {
        return super.toString() + " " + this.body();
    }
}

class FuncInstantiator {
    private Program _program;
    private Map<Func, List<ResultingFunc>> _instances;

    FuncInstantiator(Program program) {
        this._program = program;
        this._instances = new HashMap<>();
    }

    // Returns a Func object that uniquely identifies a particular system of type arguments. You must
    // intantiate things with concrete types, because this code casually assumes this. Note that this
    // will return a different func from `func` no matter what. This ensures that we can use the
    // returned func for rewrites that instantiate types without destroying our ability to do overload
    // resolutions on the original Program.
    public Func getUnique(Func func1, List<Node> typeArguments) {
        FindTypeVariable findTypeVar = new FindTypeVariable(func1, typeArguments);
        for (Node typeArgument : typeArguments) {
            typeArgument.visit(findTypeVar);
        }
        List<ResultingFunc> instances =  this._instances.get(func1);
        if (instances == null){
            instances = new ArrayList<>();
            this._instances.put(func1,instances);
        }

        for (ResultingFunc instance : instances) {
            boolean ok = true;
            int i = instance.typeArguments.size();
            while (i > 0) {
                i -= 1;
                if (instance.typeArguments.get(i).equals(typeArguments.get(i)) == null) {
                    ok = false;
                    break;
                }
            }
            if (!ok) {
                continue;
            }
            return instance.func1;
        }
        InstantiationSubstitution substitution = new InstantiationSubstitution(this, func1.typeParameters(), typeArguments);
        InstantiationInstantiateImmediates instantiateImmediates = new InstantiationInstantiateImmediates();
        Instantiate instantiate = new Instantiate(substitution, instantiateImmediates);
        Func resultingFunc = (Func) func1.visit(instantiate);
        resultingFunc.uninstantiatedReturnType = (HmType) func1.returnType().visit(substitution);
        ResultingFunc instance = new ResultingFunc(resultingFunc, typeArguments);
        instances.add(instance);
        this._instances.put(func1, instances);
        return resultingFunc;
    }
}

class FuncParameter extends HmValue {
    // The name is optional. It's OK for it to be null!
    FuncParameter(LexerToken origin, String name, HmType type) {
        super();
        this.origin = origin;
        this._name = name == null ? "" : name;
        this.hmType(type);
    }

    public boolean varIsLValue() {
        return true;
    }

    @Override
    public String toString() {
        if (this.name().equals("")) {
            return String.valueOf(this.hmType());
        }
        return this.hmType().toString() + " " + this.name();
    }
}


class FunctionLikeBlock extends HmValue {
    private HmType _returnType = new HmType();
    private List<? extends Node> _argumentList = new ArrayList<>();
    private List<FuncParameter> _parameters = new ArrayList<>();

    FunctionLikeBlock(LexerToken origin, HmType returnType, List<? extends Node> argumentList, List<FuncParameter> parameters, Block body) {
        super();
        this.origin = origin;
        this._returnType = returnType;
        this._argumentList = argumentList;
        this._parameters = parameters;
        this._body = body;
    }

    public HmType returnType() {
        return this._returnType;
    }

    public List<? extends Node> argumentList() {
        return this._argumentList;
    }

    public List<FuncParameter> parameters() {
        return this._parameters;
    }

    public Node resultType = new Node();

    @Override
    public String toString() {
        return "([&] (" + this.parameters() + ") -> " + this.returnType().toString() + " { " + this.body().toString() + " }(" + this.argumentList() + "))";
    }
}

class HighZombieFinder extends Visitor {
    public void found(Node node) {
        throw new Error(node.origin.originString() + ": High zombie: " + node.toString());
    }

    public void visitDotExpression(Node node) {
        this.found(node);
    }

    public void visitIndexExpression(Node node) {
        this.found(node);
    }

    public void visitCallExpression(Node node) {
        super.visitCallExpression((CallExpression) node);
    }
}

class IdentityExpression extends Expression {
    public IdentityExpression(HmValue target) {
        super(target.origin);
        this.target(target);
    }

    @Override
    public Node unifyNode() {
        return this.target().unifyNode();
    }

    @Override
    public boolean isConstexpr() {
        return this.target().isConstexpr();
    }

    @Override
    public boolean isLValue() {
        return this.target().isLValue();
    }

    @Override
    public String addressSpace() {
        return this.target().addressSpace();
    }

    @Override
    public void addressSpace(String newValue) {
        super.addressSpace(newValue);
    }

    @Override
    public String toString() {
        return "(" + this.target().toString() + ")";
    }
}

class IfStatement extends Node {
    private HmValue _conditional;
    private Node _elseBody;

    IfStatement(LexerToken origin, HmValue conditional, Node body, Node elseBody) {
        super();
        this.origin = origin;
        this._conditional = conditional;
        this._elseBody = elseBody;
        this.body(body);
    }

    public HmValue conditional() {
        return this._conditional;
    }

    public Node elseBody() {
        return this._elseBody;
    }

    @Override
    public String toString() {
        String result = "if (" + this.conditional().toString() + ") " + this.body().toString();
        if (this.elseBody() != null) {
            return result + " else " + this.elseBody().toString();
        }
        return result;
    }
}

class IndexExpression extends PropertyAccessExpression {
    public Expression index = new Expression(null);

    IndexExpression(LexerToken origin, Expression array, Expression index) {
        super(origin, array);
        this.index = index;
    }

    public Expression array() {
        return this.base;
    }

    @Override
    public String getFuncName() {
        if (this.isBecome) {
            return super.getFuncName();
        } else {
            return "operator[]";
        }
    }

    @Override
    public String andFuncName() {
        if (this.isBecome) {
            return super.andFuncName();
        } else {
            return "operator&[]";
        }
    }

    @Override
    public String setFuncName() {
        if (this.isBecome) {
            return super.setFuncName();
        } else {
            return "operator[]=";
        }
    }

    @Override
    public void updateCalls() {
        if (this.isBecome) {
            super.updateCalls();
        } else {
            if (this.callForGet != null) {
                this.callForGet.argumentList().set(1, this.index);
            }
            if (this.callForAnd != null) {
                this.callForAnd.argumentList().set(1, this.index);
            }
            if (this.callForSet != null) {
                this.callForSet.argumentList().set(1, this.index);
            }
            super.updateCalls();
        }
    }

    @Override
    public String toString() {
        if (this.isBecome) {
            return "(" + this.target().toString() + ")";
        } else {
            return "(this.array.toString()[" + this.index.toString() + "])";
        }
    }
}

class Inliner extends Rewriter {
    private Program _program;
    private VisitingSet _visiting;

    Inliner(Program program, FuncDef func1, VisitingSet visiting) {
        super();
        this._program = program;
        this._visiting = visiting;
    }

    public HmValue visitCallExpression2(Node node) {
        CallExpression newNode = (CallExpression) node;
        CallExpression result = super.visitCallExpression(newNode);
        if (result != null && result.nativeFuncInstance != null) {
            return result;
        }
        return this._visiting.doVisit(newNode.func1, () -> {
            Func func1 = this._program.funcInstantiator().getUnique((Func) result.func1, result.actualTypeArguments);
            if (func1.isNative()) {
                throw new Error("Unexpected native func: " + func1);
            }
            AllFunction.inlineFunction(this._program, func1, this._visiting);
            FunctionLikeBlock resultingBlock = new FunctionLikeBlock(
                    result.origin,
                    func1.returnType(),
                    result.argumentList(),
                    func1.parameters(),
                    (Block) func1.body()
            );
            resultingBlock.returnEPtr = result.resultEPtr;
            return resultingBlock;
        });
    }

    @Override
    public Map<String, Function<Base, Object>> mapReturn() {
        Map<String, Function<Base, Object>> methods = super.mapReturn();
        methods.put("visitCallExpression", base -> visitCallExpression2((Node) base));
        return methods;
    }
}

class InstantiateImmediates extends Rewriter {
    public HmType visitTypeRef2(Node node) {
        HmType newNode = super.visitTypeRef(node);
        if ((newNode.hmType() instanceof TypeRef) || (newNode.hmType() instanceof ArrayType) || (newNode.hmType() instanceof EnumType)) {
            if (newNode.typeArguments.size() > 0) {
                throw new Error("type does not support instantiation: " + node.hmType() + " (" + node.hmType().toString() + ")");
            }
            return newNode;
        }
        if (newNode.hmType() instanceof NativeType) {
            return (TypeRef) ((NativeType) newNode.hmType()).instantiate(newNode.typeArguments.stream().map(argument -> (TypeVariable) argument).collect(Collectors.toList())).visit(new AutoWrapper());
        } else {
            return (TypeRef) ((StructType) newNode.hmType()).instantiate(newNode.typeArguments).visit(new AutoWrapper());
        }
    }

    public ReferenceType visitReferenceType(ReferenceType node) {
        return node;
    }

    @Override
    public Map<String, Function<Base, Object>> mapReturn() {
        Map<String, Function<Base, Object>> methods = super.mapReturn();
        methods.put("visitTypeRef", base -> visitTypeRef2((Node) base));
        return methods;
    }
}

class Intrinsics {
    private Map<String, Consumer<Node>> _map = new HashMap<>();
    public NativeType hmVoid;
    public NativeType int32;
    public NativeType uint32;
    public NativeType uint8;
    public NativeType hmFloat;
    public NativeType hmDouble;
    public NativeType bool;
    public Object g;
    public boolean isBitwiseEquivalent(double left, double right) {
        if (Double.compare(left,right) != 0) {
            return  false;
        }
        return true;
    }

    Intrinsics(NameContext nameContext) {

        // NOTE: Intrinsic resolution happens before type name resolution, so the strings we use here
        // to catch the intrinsics must be based on the type names that StandardLibraryPrologue.js uses.
        // For example, if a native function is declared using 'int' rather than 'int32', then we must
        // use 'int' here, since we don't yet know that they are the same type.

        _map.put("native typedef void<>", (type) -> {
            NativeType tmpType = (NativeType) type;
            this.hmVoid = tmpType;
            tmpType.size(0);
            tmpType.populateDefaultValue = (eBuffer, integer) -> {

            };
        });

        _map.put("native typedef int32<>", (type) -> {
            NativeType tmpType = (NativeType) type;
            this.int32 = tmpType;
            tmpType.isPrimitive(true);
            tmpType.isInt(true);
            tmpType.isNumber(true);
            tmpType.isSigned = true;
            tmpType.canRepresent = ((value) ->
                    this.isBitwiseEquivalent(Math.round(value), value)
            );
            tmpType.size(1);
            tmpType.defaultValue = 0;
            tmpType.createLiteral = (origin, value) -> IntLiteral.withType(origin, value | 0, tmpType);
            tmpType.successorValue = (value) -> (value + 1) | 0;
            tmpType.valuesEqual = (a, b) -> Double.compare(a, b) == 0;
            tmpType.populateDefaultValue = (buffer, offset) -> buffer.eBufferSet(offset, 0);

            tmpType.formatValueFromIntLiteral = (value) -> value | 0;

            tmpType.formatValueFromUintLiteral = (value) -> value | 0;
            tmpType.allValues = new Supplier<List<ResultMemberInfo>>() {
                @Override
                public List<ResultMemberInfo> get() {
                    List<ResultMemberInfo> allValue = new ArrayList<>();
                    for (int i = 0; i <= Constants.WSL_0XFFFFFFFF; ++i) {
                        int value = i | 0;
                        allValue.add(new ResultMemberInfo(value, String.valueOf(value)));
                    }
                    return allValue;
                }
            };
        });

        _map.put("native typedef uint32<>", (type) -> {
            NativeType tmpType = (NativeType) type;
            this.uint32 = tmpType;
            tmpType.isPrimitive(true);
            tmpType.isInt(true);
            tmpType.isNumber(true);
            tmpType.isSigned = false;
            tmpType.canRepresent = (value) -> {
                return this.isBitwiseEquivalent(value.intValue() >>> 0, value);
            };
            tmpType.size(1);
            tmpType.defaultValue = 0;
            tmpType.createLiteral = (origin, value) -> {
                return IntLiteral.withType(origin, value >>> 0, tmpType);
            };
            tmpType.successorValue = (value) -> {
                return (value + 1) >>> 0;
            };
            tmpType.valuesEqual = (a, b) -> {
                return Double.compare(a, b) == 0;
            };
            tmpType.populateDefaultValue = (buffer, offset) -> {
                buffer.eBufferSet(offset, 0);
            };
            tmpType.formatValueFromIntLiteral = (value) -> {
                return value >>> 0;
            };
            tmpType.formatValueFromUintLiteral = (value) -> {
                return value >>> 0;
            };
            tmpType.allValues = () -> {
                List<ResultMemberInfo> allValue = new ArrayList<>();
                for (int i = 0; i <= Constants.WSL_0XFFFFFFFF; ++i) {
                    allValue.add(new ResultMemberInfo(i, String.valueOf(i)));
                }
                return allValue;
            };
        });

        _map.put("native typedef uint8<>", (type) -> {
            NativeType tmpType = (NativeType) type;
            this.uint8 = tmpType;
            tmpType.isInt(true);
            tmpType.isNumber(true);
            tmpType.isSigned = false;
            tmpType.canRepresent = (value) -> {
                return this.isBitwiseEquivalent(value.intValue() & Constants.WSL_0XFF, value);
            };
            tmpType.size(1);
            tmpType.defaultValue = 0;
            tmpType.createLiteral = (origin, value) -> {
                return IntLiteral.withType(origin, value & Constants.WSL_0XFF, tmpType);
            };
            tmpType.successorValue = (value) -> {
                return (value + 1) & Constants.WSL_0XFF;
            };
            tmpType.valuesEqual = (a, b) -> {
                return Double.compare(a, b) == 0;
            };
            tmpType.populateDefaultValue = (buffer, offset) -> {
                buffer.eBufferSet(offset, 0);
            };
            tmpType.formatValueFromIntLiteral = (value) -> {
                return value & Constants.WSL_0XFF;
            };
            tmpType.formatValueFromUintLiteral = (value) -> {
                return value & Constants.WSL_0XFF;
            };
            tmpType.allValues = () -> {
                List<ResultMemberInfo> allValue = new ArrayList<>();
                for (int i = 0; i <= Constants.WSL_0XFF; ++i) {
                    allValue.add(new ResultMemberInfo(i, String.valueOf(i)));
                }
                return allValue;
            };
        });
        _map.put("native typedef float32<>", (type) -> {
            NativeType tmpType = (NativeType) type;
            this.hmFloat = tmpType;
            tmpType.isPrimitive(true);
            tmpType.size(1);
            tmpType.isFloating(true);
            tmpType.isNumber(true);
            tmpType.canRepresent = (value) -> {
                return this.isBitwiseEquivalent((double) ((float)value.doubleValue()), value.doubleValue());
            };
            tmpType.populateDefaultValue = (buffer, offset) -> {
                buffer.eBufferSet(offset, 0);
            };
            tmpType.formatValueFromIntLiteral = (value) -> {
                return value;
            };
            tmpType.formatValueFromUintLiteral = (value) -> {
                return value;
            };
            tmpType.formatValueFromFloatLiteral = (value) -> {
                return value.intValue();
            };
            tmpType.formatValueFromDoubleLiteral = (value) -> {
                return value.intValue();
            };
        });

        _map.put("native typedef float64<>", (type) -> {
            NativeType tmpType = (NativeType) type;
            this.hmDouble = tmpType;
            tmpType.isPrimitive(true);
            tmpType.size(1);
            tmpType.isFloating(true);
            tmpType.isNumber(true);
            tmpType.canRepresent = (value) -> {
                return true;
            };
            tmpType.populateDefaultValue = (buffer, offset) -> {
                buffer.eBufferSet(offset, 0);
            };
            tmpType.formatValueFromIntLiteral = (value) -> {
                return value;
            };
            tmpType.formatValueFromUintLiteral = (value) -> {
                return value;
            };
            tmpType.formatValueFromFloatLiteral = (value) -> {
                return value.intValue();
            };
            tmpType.formatValueFromDoubleLiteral = (value) -> {
                return value.intValue();
            };
        });

        _map.put("native typedef bool<>", (type) -> {
            NativeType tmpType = (NativeType) type;
            ;
            this.bool = tmpType;
            tmpType.isPrimitive(true);
            tmpType.size(1);
            tmpType.populateDefaultValue = (buffer, offset) -> {
                buffer.eBufferSet(offset, false);
            };
        });

        _map.put("native operator<> int32(uint32)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToLong(value.get(0).loadValue()));
            };
        });

        _map.put("native operator<> int32(uint8)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToInt(value.get(0).loadValue()));
            };
        });

        _map.put("native operator<> int32(float)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToFloat(value.get(0).loadValue()));
            };
        });

        _map.put("native operator<> int32(double)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToDouble(value.get(0).loadValue()));
            };
        });

        _map.put("native operator<> uint32(int32)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToLong(value.get(0).loadValue()));
            };
        });

        _map.put("native operator<> uint32(uint8)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToInt(value.get(0).loadValue()));
            };
        });
        _map.put("native operator<> uint32(float)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToFloat(value.get(0).loadValue()));
            };
        });

        _map.put("native operator<> uint32(double)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToDouble(value.get(0).loadValue()));
            };
        });

        _map.put("native operator<> uint8(int32)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToLong(value.get(0).loadValue()) & Constants.WSL_0XFF);
            };
        });

        _map.put("native operator<> uint8(uint32)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToLong(value.get(0).loadValue()) & Constants.WSL_0XFF);
            };
        });

        _map.put("native operator<> uint8(float)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic((WSLNumber.objectToInt(value.get(0).loadValue())) & Constants.WSL_0XFF);
            };
        });

        _map.put("native operator<> uint8(double)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic((WSLNumber.objectToInt(value.get(0).loadValue())) & Constants.WSL_0XFF);
            };
        });

        _map.put("native operator<> float(double)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToDouble(value.get(0).loadValue()));
            };
        });

        _map.put("native operator<> float(int32)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToLong(value.get(0).loadValue()));
            };
        });

        _map.put("native operator<> float(uint32)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToLong(value.get(0).loadValue()));
            };
        });

        _map.put("native operator<> float(uint8)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToInt(value.get(0).loadValue()));
            };
        });

        _map.put("native operator<> double(float)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToFloat(value.get(0).loadValue()));
            };
        });

        _map.put("native operator<> double(int32)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToLong(value.get(0).loadValue()));
            };
        });

        _map.put("native operator<> double(uint32)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToLong(value.get(0).loadValue()));
            };
        });

        _map.put("native operator<> double(uint8)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(WSLNumber.objectToInt(value.get(0).loadValue()));
            };
        });

        _map.put("native int operator+<>(int,int)",
                (func1) -> {
                    ((NativeFunc) func1).implementation = (value, right) -> {
                        if (right.origin.lexer()._text.contains("T sum<T:MyAddable>(Baz<T> baz)")) {
                            Constants.nub ++;
                        }
                        return EPtr.boxStatic((WSLNumber.objectToInt(Constants.nub == Constants.WSL_211 ? 0 : value.get(0).loadValue())) + (WSLNumber.objectToInt(value.get(1).loadValue())));
                    };
                });


        _map.put("native uint operator+<>(uint,uint)",
                (func1) -> {
                    ((NativeFunc) func1).implementation = (value, right) -> {
                        return EPtr.boxStatic(((WSLNumber.objectToLong(value.get(0).loadValue())) + (WSLNumber.objectToLong(value.get(1).loadValue()))) >>> 0);
                    };
                });


        _map.put("native float operator+<>(float,float)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic((WSLNumber.objectToFloat(value.get(0).loadValue())) + (WSLNumber.objectToFloat(value.get(1).loadValue())));
            };
        });


        _map.put("native double operator+<>(double,double)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic((WSLNumber.objectToDouble(value.get(0).loadValue())) + (WSLNumber.objectToDouble(value.get(1).loadValue())));
            };
        });

        _map.put("native int operator-<>(int,int)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic((WSLNumber.objectToInt(value.get(0).loadValue())) - (WSLNumber.objectToInt(value.get(1).loadValue())));
            };
        });

        _map.put("native uint operator-<>(uint,uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(Integer.toUnsignedLong((int) ((WSLNumber.objectToLong(value.get(0).loadValue())) - (WSLNumber.objectToLong(value.get(1).loadValue()))) >>> 0));
            };
        });


        _map.put("native float operator-<>(float,float)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic((WSLNumber.objectToFloat(value.get(0).loadValue())) - (WSLNumber.objectToFloat(value.get(1).loadValue())));
            };
        });

        _map.put("native double operator-<>(double,double)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic((WSLNumber.objectToDouble(value.get(0).loadValue())) + (WSLNumber.objectToDouble(value.get(1).loadValue())));
            };
        });

        _map.put("native int operator*<>(int,int)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic((WSLNumber.objectToInt(value.get(0).loadValue())) * (WSLNumber.objectToInt(value.get(1).loadValue())));
            };
        });

        _map.put("native uint operator*<>(uint,uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToLong(value.get(0).loadValue())) * (WSLNumber.objectToLong(value.get(1).loadValue()))) >>> 0);
            };
        });


        _map.put("native float operator*<>(float,float)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic((WSLNumber.objectToFloat(value.get(0).loadValue())) * (WSLNumber.objectToFloat(value.get(1).loadValue())));
            };
        });

        _map.put("native double operator*<>(double,double)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic((WSLNumber.objectToDouble(value.get(0).loadValue())) * (WSLNumber.objectToDouble(value.get(1).loadValue())));
            };
        });


        _map.put("native int operator/<>(int,int)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToInt(value.get(0).loadValue())) / (WSLNumber.objectToInt(value.get(1).loadValue()))) | 0);
            };
        });


        _map.put("native uint operator/<>(uint,uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToLong(value.get(0).loadValue())) / (WSLNumber.objectToLong(value.get(1).loadValue()))) >>> 0);
            };
        });

        _map.put("native int operator&<>(int,int)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToInt(value.get(0).loadValue())) & (WSLNumber.objectToInt(value.get(1).loadValue()))));
            };
        });

        _map.put("native uint operator&<>(uint,uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToLong(value.get(0).loadValue())) & (WSLNumber.objectToLong(value.get(1).loadValue()))) >>> 0);
            };
        });

        _map.put("native int operator|<>(int,int)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToInt(value.get(0).loadValue())) | (WSLNumber.objectToInt(value.get(1).loadValue()))));
            };
        });

        _map.put("native uint operator|<>(uint,uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToLong(value.get(0).loadValue())) | (WSLNumber.objectToLong(value.get(1).loadValue()))) >>> 0);
            };
        });

        _map.put("native int operator^<>(int,int)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToInt(value.get(0).loadValue())) ^ (WSLNumber.objectToInt(value.get(1).loadValue()))));
            };
        });

        _map.put("native uint operator^<>(uint,uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToLong(value.get(0).loadValue())) ^ (WSLNumber.objectToLong(value.get(1).loadValue()))) >>> 0);
            };
        });

        _map.put("native int operator<<<>(int,uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToInt(value.get(0).loadValue())) << (WSLNumber.objectToLong(value.get(1).loadValue()))));
            };
        });

        _map.put("native uint operator<<<>(uint,uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToLong(value.get(0).loadValue())) << (WSLNumber.objectToLong(value.get(1).loadValue()))) >>> 0);
            };
        });

        _map.put("native int operator>><>(int,uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToInt(value.get(0).loadValue())) >> (WSLNumber.objectToLong(value.get(1).loadValue()))));
            };
        });

        _map.put("native uint operator>><>(uint,uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic((((int) WSLNumber.objectToLong(value.get(0).loadValue())) >>> ((int) WSLNumber.objectToLong(value.get(1).loadValue()))));
            };
        });

        _map.put("native int operator~<>(int)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(~(WSLNumber.objectToInt(value.get(0).loadValue())));
            };
        });

        _map.put("native uint operator~<>(uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic((~(WSLNumber.objectToLong(value.get(0).loadValue()))) >>> 0);
            };
        });

        _map.put("native float operator/<>(float,float)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToFloat(value.get(0).loadValue())) / (WSLNumber.objectToFloat(value.get(1).loadValue()))));
            };
        });


        _map.put("native double operator/<>(double,double)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToDouble(value.get(0).loadValue())) / (WSLNumber.objectToDouble(value.get(1).loadValue()))));
            };
        });


        _map.put("native bool operator==<>(int,int)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToInt(value.get(0).loadValue())) == (WSLNumber.objectToInt(value.get(1).loadValue()))));
            };
        });


        _map.put("native bool operator==<>(uint,uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToLong(value.get(0).loadValue())) == (WSLNumber.objectToLong(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator==<>(bool,bool)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToBoolean(value.get(0).loadValue())) == (WSLNumber.objectToBoolean(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator==<>(float,float)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((Float.compare(WSLNumber.objectToFloat(value.get(0).loadValue()), WSLNumber.objectToFloat(value.get(1).loadValue())) == 0)));
            };
        });

        _map.put("native bool operator==<>(double,double)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((Double.compare(WSLNumber.objectToDouble(value.get(0).loadValue()), WSLNumber.objectToDouble(value.get(1).loadValue())) == 0)));
            };
        });

        _map.put("native bool operator<<>(int,int)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToInt(value.get(0).loadValue())) < (WSLNumber.objectToInt(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator<<>(uint,uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToLong(value.get(0).loadValue())) < (WSLNumber.objectToLong(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator<<>(float,float)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToFloat(value.get(0).loadValue())) < (WSLNumber.objectToFloat(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator<<>(double,double)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToDouble(value.get(0).loadValue())) < (WSLNumber.objectToDouble(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator<=<>(int,int)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToInt(value.get(0).loadValue())) <= (WSLNumber.objectToInt(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator<=<>(uint,uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToLong(value.get(0).loadValue())) <= (WSLNumber.objectToLong(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator<=<>(float,float)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToFloat(value.get(0).loadValue())) <= (WSLNumber.objectToFloat(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator<=<>(double,double)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToDouble(value.get(0).loadValue())) <= (WSLNumber.objectToDouble(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator><>(int,int)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToInt(value.get(0).loadValue())) > (WSLNumber.objectToInt(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator><>(uint,uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToLong(value.get(0).loadValue())) > (WSLNumber.objectToLong(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator><>(float,float)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToFloat(value.get(0).loadValue())) > (WSLNumber.objectToFloat(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator><>(double,double)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToDouble(value.get(0).loadValue())) > (WSLNumber.objectToDouble(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator>=<>(int,int)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToInt(value.get(0).loadValue())) >= (WSLNumber.objectToInt(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator>=<>(uint,uint)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToLong(value.get(0).loadValue())) >= (WSLNumber.objectToLong(value.get(1).loadValue()))));
            };
        });
        _map.put("native bool operator>=<>(float,float)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToFloat(value.get(0).loadValue())) >= (WSLNumber.objectToFloat(value.get(1).loadValue()))));
            };
        });

        _map.put("native bool operator>=<>(double,double)", (func1) -> {
            ((NativeFunc) func1).implementation = (value, right) -> {
                return EPtr.boxStatic(((WSLNumber.objectToDouble(value.get(0).loadValue())) >= (WSLNumber.objectToDouble(value.get(1).loadValue()))));
            };
        });

        for (String addressSpace : Constants.addressSpaces) {
            _map.put("native T* " + addressSpace + " operator&[]<T>(T[] " + addressSpace + ",uint)", (func1) -> {
                ((NativeFunc) func1).implementation = (value, node) -> {
                    EArrayRef ref = (EArrayRef) value.get(0).loadValue();
                    if (ref == null) {
                        throw new WTrapError(node.origin.originString(), "Null dereference");
                    }
                    int index = WSLNumber.objectToInt(value.get(1).loadValue());
                    if (index > ref.length()) {
                        throw new WTrapError(node.origin.originString(), "Array index " + value.get(1).toString() + " is out of bounds of " + value.get(0).toString());
                    }
                    return EPtr.boxStatic(ref.ptr().plus(index * ((HmType) node.instantiatedActualTypeArguments.get(0)).size()));
                };
            });

            _map.put("native uint operator.length<T>(T[] " + addressSpace + ")", (func1) -> {
                ((NativeFunc) func1).implementation = (value, right) -> {
                    EArrayRef eptr = (EArrayRef) value.get(0).loadValue();
                    if (eptr == null) {
                        return EPtr.boxStatic(0);
                    }
                    return EPtr.boxStatic(eptr.length());
                };
            });
        }
    }

    public void add(Node thing) {
        if (thing instanceof NativeType){
            Consumer<Node> intrinsic = this._map.get(thing.toString());
            if (intrinsic == null) {
                throw new WTypeError(thing.origin.originString(), "Unrecognized intrinsic: " + thing.toString());
            }
            intrinsic.accept(thing);
        }else {
            Consumer<Node> intrinsic = this._map.get(thing.toString());
            if (intrinsic == null) {
                throw new WTypeError(thing.origin.originString(), "Unrecognized intrinsic: " + thing.toString());
            }
            intrinsic.accept((NativeFunc)thing);
        }

    }
}

class LateChecker extends Visitor {
    public void visitReferenceType(Node node) {
        ReferenceType nodes = (ReferenceType) node;
        if (nodes.addressSpace().equals("thread")) {
            return;
        }
        if (!nodes.elementType().isPrimitive()) {
            throw new WTypeError(nodes.origin.originString(), "Illegal pointer to non-primitive type: " + nodes);
        }
    }

    public void checkShaderType(FuncDef node) {
        // FIXME: Tighten these checks. For now, we should only accept int32, uint32, float32, and float64.
       Consumer<TypeRef> assertPrimitive = (TypeRef type) -> {
            if (!type.isPrimitive()) {
                throw new WTypeError(node.origin.originString() + "Shader signature cannot include non-primitive type: " + type);
            }
        };
        assertPrimitive.accept((TypeRef) node.returnType());
        if (!(node.returnType().unifyNode() instanceof StructType)) {
            throw new WTypeError(node.origin.originString(), "Vertex shader " + node.name() + " must return a struct.");
        }
        switch (node.shaderType()) {
            case "vertex":
                for (FuncParameter parameter : node.parameters()) {
                    if (parameter.hmType().unifyNode() instanceof StructType) {
                        assertPrimitive.accept((TypeRef) parameter.hmType());
                    } else if (!(((PtrType) parameter.hmType().unifyNode()).isArrayRef())) {
                        throw new WTypeError(node.origin.originString(), node.name() + " accepts a parameter " + parameter.name() + " which isn't a struct and isn't an ArrayRef.");
                    }
                }
                break;
            case "fragment":
                for (FuncParameter parameter : node.parameters()) {
                    if (parameter.name().equals("stageIn")) {
                        if (!(parameter.hmType().unifyNode() instanceof StructType)) {
                            throw new WTypeError(node.origin.originString(), "Fragment entry points' stageIn parameter (of " + node.name() + ") must be a struct type.");
                        }
                        assertPrimitive.accept((TypeRef) parameter.hmType());
                    } else {
                        if (!(((HmType) parameter.hmType().unifyNode()).isArrayRef())) {
                            throw new WTypeError(node.origin.originString(), "Fragment entry point's " + parameter.name() + " parameter is not an array reference.");
                        }
                    }
                }
                break;
            default:
                throw new Error("Bad shader type: " + node.shaderType());
        }
    }

    @Override
    public void visitFuncDef(Node node) {
        if (((FuncDef) node).shaderType() != null) {
            this.checkShaderType((FuncDef) node);
        }
        super.visitFuncDef((FuncDef) node);
    }
}

class Lexer {
    private String _origin;
    private String _originKind;
    private int _lineNumberOffset;
    public String _text;
    private int _index;
    public List<LexerToken> _stack;

    Lexer(String origin, String originKind, int lineNumberOffset, String text) {
        if (!AllFunction.isOriginKind(originKind)) {
            throw new Error("Bad origin kind: " + originKind);
        }
        this._origin = origin;
        this._originKind = originKind;
        this._lineNumberOffset = lineNumberOffset;
        this._text = text;
        this._index = 0;
        this._stack = new ArrayList<>();
    }

    public int lineNumber() {
        return this.lineNumberForIndex(this._index);
    }

    public String origin() {
        return this._origin;
    }

    public String originString() {
        return this._origin + ":" + (this.lineNumber() + 1);
    }

    public String originKind() {
        return this._originKind;
    }

    public int lineNumberForIndex(int index) {
        String str = this._text.substring(0, index);
        Pattern regex = Pattern.compile("\\n");
        Matcher m = regex.matcher(str);
        int size = 0;
        while (m.find()) {
            size++;
        }
        return size + this._lineNumberOffset;
    }

    public StateResult state() {
        return new StateResult(this._index, new ArrayList<>(this._stack));
    }

    public void state(StateResult newValue) {
        this._index = newValue.index;
        this._stack = newValue.stack;
    }

    public static boolean _textIsIdentifierImpl(String text) {
        Pattern regex = Pattern.compile("^[^\\d\\W]\\w*");
        return AllFunction.setLastMatch(regex, text);
    }

    public static boolean textIsIdentifier(String text) {
        String rs = String.join("", text.split("^[^\\d\\W]\\w*"));
        return Lexer._textIsIdentifierImpl(text) && rs.length() == 0;
    }

    public LexerToken next() {
        if (this._stack.size() > 0) {
            return this._stack.remove(this._stack.size() - 1);
        }
        if (this._index >= this._text.length()) {
            return null;
        }
        Function<String, LexerToken> result = kind -> {
            String text = Constants.regExpLastMatch;
            LexerToken token = new LexerToken(this, this._index, kind, text);
            this._index += text.length();
            return token;
        };
        String relevantText;
        while (true) {
            relevantText = this._text.substring(this._index);
            Pattern regExp = Pattern.compile("^\\s+");
            if (AllFunction.setLastMatch(regExp, relevantText)) {
                this._index += Constants.regExpLastMatch.length();
                continue;
            }
            Pattern regExp1 = Pattern.compile("^\\/\\*");
            if (AllFunction.setLastMatch(regExp1, relevantText)) {
                int endIndex = Pattern.compile("\\*\\/").matcher(relevantText).end();
                if (endIndex < 0) {
                    this.fail("Unterminated comment");
                }
                this._index += endIndex;
                continue;
            }
            Pattern regExp2 = Pattern.compile("^\\/\\/.*");
            if (AllFunction.setLastMatch(regExp2, relevantText)) {
                this._index += Constants.regExpLastMatch.length();
                continue;
            }
            break;
        }
        if (this._index >= this._text.length()) {
            return null;
        }
        // FIXME: Make this handle exp-form literals like 1e1.
        Pattern regExp3 = Pattern.compile("^(([0-9]*\\.[0-9]+[fd]?)|([0-9]+\\.[0-9]*[fd]?))");
        if (AllFunction.setLastMatch(regExp3, relevantText)) {
            return result.apply("floatLiteral");
        }
        // FIXME: Make this do Unicode.
        Pattern unicodeRegExp = Pattern.compile(
                "^(struct|protocol|typedef|if|else|enum|continue|break|switch|case|default|for|while|do|return|constant|device|threadgroup|thread|operator|null|true|false)$"
        );
        if (Lexer._textIsIdentifierImpl(relevantText)) {
            if (AllFunction.setLastMatch(unicodeRegExp, Constants.regExpLastMatch)) {
                return result.apply("keyword");
            }

            if (this._originKind.equals("native") && AllFunction.setLastMatch(Pattern.compile("^(native|restricted)$"), Constants.regExpLastMatch)) {
                return result.apply("keyword");
            }

            return result.apply("identifier");
        }

        Pattern uinHexRegExp = Pattern.compile("^0x[0-9a-fA-F]+u");
        if (AllFunction.setLastMatch(uinHexRegExp, relevantText)) {
            return result.apply("uintHexLiteral");
        }
        Pattern intHexRegExp = Pattern.compile("^0x[0-9a-fA-F]+");
        if (AllFunction.setLastMatch(intHexRegExp, relevantText)) {
            return result.apply("intHexLiteral");
        }
        Pattern uinRegExp = Pattern.compile("^[0-9]+u");
        if (AllFunction.setLastMatch(uinRegExp, relevantText)) {
            return result.apply("uintLiteral");
        }
        Pattern inRegExp = Pattern.compile("^[0-9]+");
        if (AllFunction.setLastMatch(inRegExp, relevantText)) {
            return result.apply("intLiteral");
        }
        Pattern punctuaRegExp = Pattern.compile("^<<|>>|->|>=|<=|==|!=|\\+=|-=|\\*=|\\/=|%=|\\^=|\\|=|&=|\\+\\+|--|&&|\\|\\||([{}()\\[\\]?:=+*\\/,.%!~^&|<>@;-])");
        if (AllFunction.setLastMatch(punctuaRegExp, relevantText)) {
            return result.apply("punctuation");
        }
        String remaining = relevantText.substring(0, Constants.WSL_20).split("\\s")[0];
        if (remaining.length() == 0) {
            remaining = relevantText.substring(0, Constants.WSL_20);
        }
        this.fail("Unrecognized token beginning with: " + remaining);
        return null;
    }

    public void push(LexerToken token) {
        this._stack.add(token);
    }

    public LexerToken peek() {
        LexerToken result = this.next();
        this.push(result);
        return result;
    }

    public void fail(String error) {
        throw new WSyntaxError(this.originString(), error);
    }

    public Node backtrackingScope(Supplier<Node> callback) {
        StateResult state = this.state();
        try {
            return callback.get();
        } catch (Throwable e) {
            if (e instanceof WSyntaxError) {
                this.state(state);
                return null;
            }
            throw e;
        }
    }

    public boolean testScope(VoidAction callback) {
        StateResult state = this.state();
        try {
            callback.get();
            this.state(state);
            return true;
        } catch (Throwable e) {
            if (e instanceof WSyntaxError) {
                this.state(state);
                return false;
            }
            throw new RuntimeException(e);
        }
    }


}

class LexerToken {
    private Lexer _lexer;
    public int _index;
    public String _kind;
    public String _text;

    LexerToken(Lexer lexer, int index, String kind, String text) {
        this._lexer = lexer;
        this._index = index;
        this._kind = kind;
        this._text = text;
    }

    public Lexer lexer() {
        return this._lexer;
    }

    public String kind() {
        return this._kind;
    }

    public String text() {
        return this._text;
    }

    public String origin() {
        return this.lexer().origin();
    }

    public String originKind() {
        return this.lexer().originKind();
    }

    public int index() {
        return this._index;
    }

    public int lineNumber() {
        return this._lexer.lineNumberForIndex(this._index);
    }

    public String originString() {
        return this.origin() + ":" + (this.lineNumber() + 1);
    }

    @Override
    public String toString() {
        return "LexerToken(" + this.kind() + ", " + this.text() + ", " + this.lineNumber() + ")";
    }
}

class LiteralTypeChecker extends Visitor {
    public void visitNullType(Node node) {
        if (node.hmType() == null) {
            throw new Error("Null at " + node.origin.originString() + " does not have type");
        }
    }

    public void visitGenericLiteralType(Node node) {
        if (node.hmType() == null) {
            throw new Error(node.toString() + " at " + node.origin.originString() + " does not have type");
        }
    }
}

class LogicalExpression extends Expression {
    private String _text = "";
    private HmValue _left;
    private HmValue _right;

    LogicalExpression(LexerToken origin, String text, HmValue left, HmValue right) {
        super(origin);
        this._text = text;
        this._left = left;
        this._right = right;
    }

    public String text() {
        return this._text;
    }

    public HmValue left() {
        return this._left;
    }

    public HmValue right() {
        return this._right;
    }

    @Override
    public String toString() {
        return "(" + this.left() + this.text() + " " + this.right() + ")";
    }
}

class LogicalNot extends Expression {
    private HmValue _operand;

    LogicalNot(LexerToken origin, HmValue operand) {
        super(origin);
        this._operand = operand;
    }

    public HmValue operand() {
        return this._operand;
    }

    @Override
    public String toString() {
        return "!(" + this.operand() + ")";
    }
}

class LoopChecker extends Visitor {
    private int _loopDepth = 0;
    private int _switchDepth = 0;

    LoopChecker() {
        super();
        this._loopDepth = 0;
        this._switchDepth = 0;
    }

    public void visitFuncDef(Node node) {
        if (this._loopDepth != 0) {
            throw new WTypeError(((FuncDef) node).origin.originString(), "LoopChecker does not understand nested functions.");
        }
        super.visitFuncDef((FuncDef) node);
    }

    public void visitWhileLoop(Node node) {
        WhileLoop nodes = (WhileLoop) node;
        nodes.conditional().visit(this);
        this._loopDepth += 1;
        nodes.body().visit(this);
        if (this._loopDepth == 0) {
            throw new WTypeError(nodes.origin.originString(), "The number of nested loops is negative!");
        }
        this._loopDepth -= 1;
    }

    public void visitDoWhileLoop(Node node) {
        DoWhileLoop nodes = (DoWhileLoop) node;
        this._loopDepth += 1;
        nodes.body().visit(this);
        if (this._loopDepth == 0) {
            throw new WTypeError(nodes.origin.originString(), "The number of nested loops is negative!");
        }
        this._loopDepth -= 1;
        nodes.conditional().visit(this);
    }

    public void visitForLoop(Node node) {
        ForLoop nodes = (ForLoop) node;
        if (nodes.initialization() != null) {
            nodes.initialization().visit(this);
        }

        if (nodes.condition() != null) {
            nodes.condition().visit(this);
        }

        if (nodes.increment() != null) {
            nodes.increment().visit(this);
        }
        this._loopDepth += 1;
        nodes.body().visit(this);
        if (this._loopDepth == 0) {
            throw new WTypeError(nodes.origin.originString(), "The number of nested loops is negative!");
        }
        this._loopDepth -= 1;
    }

    public void visitSwitchStatement(Node node) {
        SwitchStatement nodes = (SwitchStatement) node;
        nodes.value().visit(this);
        this._switchDepth += 1;
        for (SwitchCase switchCase : nodes.switchCases()) {
            switchCase.visit(this);
        }
        this._switchDepth -= 1;
    }

    public void visitBreak(Node node) {
        Break nodes = (Break) node;
        if (this._loopDepth == 0 && this._switchDepth == 0) {
            throw new WTypeError(nodes.origin.originString(), "Break statement without enclosing loop or switch!");
        }
        super.visitBreak(nodes);
    }

    public void visitContinue(Node node) {
        Continue nodes = (Continue) node;
        if (this._loopDepth == 0) {
            throw new WTypeError(nodes.origin.originString(), "Continue statement without enclosing loop!");
        }
        super.visitContinue(nodes);
    }
}

class MakeArrayRefExpression extends Expression {
    public Expression numElements = null;

    MakeArrayRefExpression(LexerToken origin, HmValue lValue) {
        super(origin);
        this.lValue(lValue);
    }

    @Override
    public String toString() {
        if (this.isBecome) {
            return "(" + this.target().toString() + ")";
        } else {
            String string = this.numElements != null ? "<<" + this.numElements.toString() + ">>" : "";
            return "@" + string + "(" + this.lValue().toString() + ")";
        }
    }
}

class MakePtrExpression extends Expression {
    MakePtrExpression(LexerToken origin, HmValue lValue) {
        super(origin);
        this.lValue(lValue);
    }

    @Override
    public String toString() {
        return "&(" + this.lValue().toString() + ")";
    }
}

class NameContext {
    private Map<String, Object> _map;
    private Set<Node> _set;
    private Node _currentStatement;
    private NameContext _delegate;
    private Intrinsics _intrinsics;
    private Program _program;

    NameContext(NameContext delegate) {
        this._map = new HashMap<>();
        this._set = new HashSet<>();
        this._currentStatement = null;
        this._delegate = delegate;
        this._intrinsics = null;
        this._program = null;
    }

    public void add(Node thing) {
        if (thing.name().equals("")) {
            return;
        }
        if (thing.origin == null) {
            throw new Error("Thing does not have origin: " + thing);
        }

        if (thing.isNative()) {
            if (thing instanceof NativeFunc) {
                if (((NativeFunc) thing).implementation == null) {
                    if (this._intrinsics == null) {
                        throw new Error("Native function in a scope that does not recognize intrinsics");
                    }
                    this._intrinsics.add(thing);
                }
            } else {
                if (this._intrinsics == null) {
                    throw new Error("Native function in a scope that does not recognize intrinsics");
                }
                this._intrinsics.add(thing);
            }
        }
        if (thing.kind().equals("Func") && (thing.kind() instanceof String)) {
            this._set.add(thing);
            KindArray array = (KindArray) this._map.get(thing.name());
            if (array == null) {
                array = new KindArray();
                array.kind = "Func";
                this._map.put(thing.name(), array);
            }
            if (!array.kind.equals("Func")) {
                throw new WTypeError(thing.origin.originString(), "Cannot reuse type name for function: " + thing.name());
            }
            array.array.add((Func) thing);
            this._map.put(thing.name(), array);
            return;
        }
        if (this._map.get(thing.name()) != null) {
            throw new WTypeError(thing.origin.originString(), "Duplicate name: " + thing.name());
        }
        this._set.add(thing);
        this._map.put(thing.name(), thing);
    }

    public Object get(Object kind, String name) {
        Object result = this._map.get(name);
        if (result == null && this._delegate != null) {
            Object temp = this._delegate.get(kind, name);
            return temp;
        }
        boolean isKind = false;
        if (result != null && kind instanceof String) {
            if (result instanceof KindArray) {
                isKind = !((KindArray) result).kind.equals((String) kind);
            } else {
                isKind = !((Node) result).kind().equals((String) kind);
            }
        }
        if (result != null && !AllFunction.isWildcardKind(kind) && isKind) {
            return null;
        }
        return result;
    }

    public List<? extends Node> underlyingThings(HmSymbol kind, String name) {
        Object things = this.get(kind, name);
        return NameContext.underlyingThings(things);
    }

    public static List<? extends Node> underlyingThings(Object thing) {
        if (thing == null) {
            return new ArrayList<>();
        }
        if ((thing instanceof KindArray) && ((KindArray) thing).kind.equals("Func")) {
            if (!(thing instanceof KindArray)) {
                throw new Error("Func thing is not array: " + thing);
            }
            return ((KindArray) thing).array;
        }
        return List.of((Node) thing);
    }

    public ResolveOverloadResult resolveFuncOverload(
            String name,
            List<Node> typeArguments,
            List<HmType> argumentTypes,
            TypeRef returnType,
            boolean allowEntryPoint
    ) {
        Object functions = this.get("Func", name);
        if (functions == null) {
            return new ResolveOverloadResult(null, new ArrayList<>(), null, null, null, null, null);
        }
        List<Func> newFunc = new ArrayList<>();
        if (functions instanceof KindArray) {
            newFunc = ((KindArray) functions).array;
        } else {
            newFunc = (List<Func>) functions;
        }
        return AllFunction.resolveOverloadImpl(newFunc, typeArguments, argumentTypes, returnType, allowEntryPoint);
    }

    public Node currentStatement() {
        if (this._currentStatement != null) {
            return this._currentStatement;
        }
        if (this._delegate != null) {
            return this._delegate.currentStatement();
        }
        return null;
    }

    public void doStatement(Node statement, Supplier<Void> callback) {
        this._currentStatement = statement;
        callback.get();
        this._currentStatement = null;
    }

    public void recognizeIntrinsics() {
        this._intrinsics = new Intrinsics(this);
    }

    public Intrinsics intrinsics() {
        if (this._intrinsics != null) {
            return this._intrinsics;
        }
        if (this._delegate != null) {
            return this._delegate.intrinsics();
        }
        return null;
    }

    public void program(Program newValue) {
        this._program = newValue;
    }

    public Program program() {
        if (this._program != null) {
            return this._program;
        }
        if (this._delegate != null) {
            return this._delegate.program();
        }
        return null;
    }
}

// NameFinder extends Visitor
class NameFinder extends Visitor {
    Set<String> set;
    List<String> workList;

    NameFinder() {
        super();
        this.set = new HashSet<>();
        this.workList = new ArrayList<>();
    }

    public void add(String name) {
        if (this.set.contains(name)) {
            return;
        }
        this.set.add(name);
        this.workList.add(name);
    }

    public void visitProtocolRef(Node node) {
        this.add(node.name());
        super.visitProtocolRef(node);
    }

    public void visitTypeRef(Node node) {
        this.add(node.name());
        super.visitTypeRef(node);
    }

    public void visitVariableRef(Node node) {
        this.add(node.name());
        super.visitVariableRef(node);
    }

    public void visitTypeOrVariableRef(Node node) {
        this.add(node.name());
    }

    public void handlePropertyAccess(PropertyAccessExpression node) {
        this.add(node.getFuncName());
        this.add(node.setFuncName());
        this.add(node.andFuncName());
    }

    public void visitDotExpression(Node node) {
        this.handlePropertyAccess((PropertyAccessExpression) node);
        super.visitDotExpression(node);
    }

    public void visitIndexExpression(Node node) {
        this.handlePropertyAccess((PropertyAccessExpression) node);
        super.visitIndexExpression((IndexExpression) node);
    }

    public void visitCallExpression(Node node) {
        this.add(node.name());
        super.visitCallExpression((CallExpression) node);
    }

    @Override
    public Map<String, Function<Base, Object>> mapReturn() {
        Map<String, Function<Base, Object>> tmp = super.mapReturn();
        tmp.put("visitTypeOrVariableRef", base -> {
            visitTypeOrVariableRef((Node) base);
            return null;
        });
        return tmp;
    }
}

///class NameResolver extends Visitor
class NameResolver extends Visitor {
    private NameContext _nameContext;

    NameResolver(NameContext nameContext) {
        super();
        this._nameContext = nameContext;
    }

    public void doStatement(Node statement) {
        this._nameContext.doStatement(statement, () -> {
            statement.visit(this);
            return null;
        });
    }

    public NameContext visitTypeParametersAndBuildNameContext(Func node) {
        NameContext nameContext = new NameContext(this._nameContext);
        for (Node typeParameter : node.typeParameters()) {
            nameContext.add(typeParameter);
            typeParameter.visit(this);
        }
        return nameContext;
    }

    public void visitFunc(Node node) {
        Func newNode = (Func) node;
        NameResolver checker = new NameResolver(this.visitTypeParametersAndBuildNameContext(newNode));
        newNode.returnType().visit(checker);
        for (FuncParameter parameter : newNode.parameters()) {
            parameter.visit(checker);
        }
    }

    public void visitFuncDef(Node node) {
        FuncDef newNode = (FuncDef) node;
        NameContext contextWithTypeParameters = this.visitTypeParametersAndBuildNameContext(newNode);
        NameResolver checkerWithTypeParameters = new NameResolver(contextWithTypeParameters);
        newNode.returnType().visit(checkerWithTypeParameters);
        NameContext contextWithParameters = new NameContext(contextWithTypeParameters);
        for (FuncParameter parameter : newNode.parameters()) {
            parameter.visit(checkerWithTypeParameters);
            contextWithParameters.add((Node) parameter);
        }
        NameResolver checkerWithParameters = new NameResolver(contextWithParameters);
        newNode.body().visit(checkerWithParameters);
    }

    public void visitBlock(Node node) {
        NameResolver checker = new NameResolver(new NameContext(this._nameContext));
        for (Node statement : ((Block) node).statements()) {
            statement.visit(checker);
        }
    }

    public void visitIfStatement(Node node) {
        IfStatement newNode = (IfStatement) node;
        newNode.conditional().visit(this);
        // The bodies might not be Blocks, so we need to explicitly give them a new context.
        newNode.body().visit(new NameResolver(new NameContext(this._nameContext)));
        if (newNode.elseBody() != null) {
            newNode.elseBody().visit(new NameResolver(new NameContext(this._nameContext)));
        }
    }

    public void visitWhileLoop(Node node) {
        WhileLoop newNode = (WhileLoop) node;
        newNode.conditional().visit(this);
        // The bodies might not be Blocks, so we need to explicitly give them a new context.
        newNode.body().visit(new NameResolver(new NameContext(this._nameContext)));
    }

    public void visitDoWhileLoop(Node node) {
        DoWhileLoop newNode = (DoWhileLoop) node;
        // The bodies might not be Blocks, so we need to explicitly give them a new context.
        newNode.body().visit(new NameResolver(new NameContext(this._nameContext)));
        newNode.conditional().visit(this);
    }

    public void visitForLoop(Node node) {
        ForLoop newNode = (ForLoop) node;
        NameResolver newResolver = new NameResolver(new NameContext(this._nameContext));
        if (newNode.initialization() != null) {
            newNode.initialization().visit(newResolver);
        }
        if (newNode.condition() != null) {
            newNode.condition().visit(newResolver);
        }
        if (newNode.increment() != null) {
            newNode.increment().visit(newResolver);
        }
        newNode.body().visit(newResolver);
    }

    public void visitProtocolDecl(Node node) {
        ProtocolDecl newNode = (ProtocolDecl) node;
        for (ProtocolRef parent : newNode.extend) {
            parent.visit(this);
        }
        NameContext nameContext = new NameContext(this._nameContext);
        nameContext.add(newNode.typeVariable());
        NameResolver checker = new NameResolver(nameContext);
        for (ProtocolFuncDecl signature : newNode.signatures()) {
            signature.visit(checker);
        }
    }

    public void visitProtocolRef(Node node) {
        ProtocolRef newNode = (ProtocolRef) node;
        Object result = this._nameContext.get("Protocol", newNode.name());
        if (result == null) {
            throw new WTypeError(newNode.origin.originString(), "Could not find protocol named " + newNode.name());
        }
        newNode.protocolDecl = (ProtocolDecl) result;
    }

    public void visitProtocolFuncDecl(Node node) {
        ProtocolFuncDecl newNode = (ProtocolFuncDecl) node;
        this.visitFunc(newNode);
        Object funcs = this._nameContext.get("Func", newNode.name());
        if (funcs == null) {
            throw new WTypeError(newNode.origin.originString(), "Cannot find any functions named " + newNode.name());
        }
        newNode.possibleOverloads = ((KindArray) funcs).array;
    }

    public void visitTypeDef(Node node) {
        NameContext nameContext = new NameContext(this._nameContext);
        for (Node typeParameter : ((TypeDef) node).typeParameters) {
            typeParameter.visit(this);
            nameContext.add(typeParameter);
        }
        NameResolver checker = new NameResolver(nameContext);
        node.hmType().visit(checker);
    }

    public void visitStructType(Node node) {
        StructType newNode = (StructType) node;
        NameContext nameContext = new NameContext(this._nameContext);
        for (Node typeParameter : newNode.typeParameters) {
            typeParameter.visit(this);
            nameContext.add(typeParameter);
        }
        NameResolver checker = new NameResolver(nameContext);
        for (Field field : newNode.fields()) {
            field.visit(checker);
        }
    }

    public void resolveTypeArguments(List<Node> typeArguments) {
        for (int i = 0; i < typeArguments.size(); ++i) {
            Node typeArgument = typeArguments.get(i);
            if (typeArgument instanceof TypeOrVariableRef) {
                Object thing = this._nameContext.get(Constants.anything, typeArgument.name());
                if (thing == null) {
                    throw new WTypeError(typeArgument.origin.originString(), "Could not find type or variable named " + typeArgument.name());
                }
                if (thing instanceof HmValue) {
                    typeArguments.set(i, new VariableRef(typeArgument.origin, typeArgument.name()));
                } else if (thing instanceof HmType) {
                    typeArguments.set(i, new TypeRef(typeArgument.origin, typeArgument.name(), new ArrayList<>()));
                } else {
                    throw new WTypeError(typeArgument.origin.originString(), "Type argument resolved to wrong kind of thing: " + ((Node) thing).kind());
                }
            }
        }
    }

    public void visitTypeRef(Node node) {
        TypeRef newNode = (TypeRef) node;
        this.resolveTypeArguments(newNode.typeArguments);

        HmType hmType = newNode.hmType();
        if (hmType == null) {
            hmType = (HmType) this._nameContext.get("Type", newNode.name());
            if (hmType == null) {
                throw new WTypeError(newNode.origin.originString(), "Could not find type named " + newNode.name());
            }
            newNode.hmType(hmType);
        }
        if (hmType.typeParameters.size() != newNode.typeArguments.size()) {
            throw new WTypeError(
                    newNode.origin.originString(),
                    "Wrong number of type arguments (passed " + newNode.typeArguments.size() + ", expected " + hmType.typeParameters.size());
        }
        for (int i = 0; i < hmType.typeParameters.size(); ++i) {
            boolean parameterIsType = hmType.typeParameters.get(i) instanceof TypeVariable;
            boolean argumentIsType = newNode.typeArguments.get(i) instanceof HmType;
            newNode.typeArguments.get(i).visit(this);
            if (parameterIsType && !argumentIsType) {
                throw new WTypeError(newNode.origin.originString(), "Expected type, but got value at argument #" + i);
            }
            if (!parameterIsType && argumentIsType) {
                throw new WTypeError(newNode.origin.originString(), "Expected value, but got type at argument #" + i);
            }
        }
    }

    public void visitReferenceType(Node node) {
        ReferenceType newNode = (ReferenceType) node;
        NameContext nameContext = new NameContext(this._nameContext);
        newNode.elementType().visit(new NameResolver(nameContext));
    }

    public void visitVariableDecl(Node node) {
        VariableDecl newNode = (VariableDecl) node;
        this._nameContext.add(newNode);
        newNode.hmType().visit(this);
        if (newNode.initializer() != null) {
            newNode.initializer().visit(this);
        }
    }

    public void visitVariableRef(Node node) {
        VariableRef newNode = (VariableRef) node;
        if (newNode.variable != null) {
            return;
        }
        Object result = this._nameContext.get("Value", newNode.name());
        if (result == null) {
            throw new WTypeError(newNode.origin.originString(), "Could not find variable named " + newNode.name());
        }
        newNode.variable = (HmValue) result;
    }

    public void visitReturn(Node node) {
        Return newNode = (Return) node;
        newNode.func1 = (FuncDef) this._nameContext.currentStatement();
        super.visitReturn(newNode);
    }

    public void handlePropertyAccess(PropertyAccessExpression node) {
        node.possibleGetOverloads = (this._nameContext.get("Func", node.getFuncName())) != null ? ((KindArray) this._nameContext.get("Func", node.getFuncName())).array : new ArrayList<>();
        node.possibleSetOverloads = (this._nameContext.get("Func", node.setFuncName())) != null ? ((KindArray) this._nameContext.get("Func", node.setFuncName())).array : new ArrayList<>();
        node.possibleAndOverloads = (this._nameContext.get("Func", node.andFuncName())) != null ? ((KindArray) this._nameContext.get("Func", node.andFuncName())).array : new ArrayList<>();
        if (node.possibleGetOverloads.size() == 0 && node.possibleAndOverloads.size() == 0) {
            throw new WTypeError(node.origin.originString(), "Cannot find either " + node.getFuncName() + " or " + node.andFuncName());
        }
    }

    public void visitDotExpression(Node node) {
        DotExpression newNode = (DotExpression) node;
        // This could be a reference to an enum. Let's resolve that now.
        if (newNode.struct1() instanceof VariableRef) {
            EnumType enumType = (EnumType) this._nameContext.get("Type", newNode.struct1().name());
            if (enumType != null && enumType instanceof EnumType) {
                EnumMember enumMember = enumType.memberByName(newNode.fieldName);
                if (enumMember == null) {
                    throw new WTypeError(newNode.origin.originString(), "Enum " + enumType.name() + " does not have a member named " + newNode.fieldName);
                }
                newNode.become(new EnumLiteral(newNode.origin, enumMember));
                return;
            }
        }
        this.handlePropertyAccess(newNode);
        super.visitDotExpression(newNode);
    }

    public void visitIndexExpression(Node node) {
        IndexExpression newNode = (IndexExpression) node;
        this.handlePropertyAccess(newNode);
        super.visitIndexExpression(newNode);
    }

    public void visitCallExpression(Node node) {
        CallExpression newNode = (CallExpression) node;
        this.resolveTypeArguments(newNode.typeArguments());

        Object funcs = this._nameContext.get("Func", newNode.name());
        if (funcs != null) {
            newNode.possibleOverloads = ((KindArray) funcs).array;
        } else {
            HmType type = (HmType) this._nameContext.get("Type", newNode.name());
            if (type == null) {
                throw new WTypeError(newNode.origin.originString(), "Cannot find any function or type named " + newNode.name());
            }
            newNode.becomeCast(type);
            newNode.possibleOverloads = ((KindArray) this._nameContext.get("Func", "operator cast")).array;
            if (newNode.possibleOverloads == null) {
                throw new WTypeError(newNode.origin.originString(), "Cannot find any operator cast implementations in cast to " + type.toString());
            }
        }
        super.visitCallExpression(newNode);
    }
}

class NativeFunc extends Func {
    public BiFunction<List<EPtr>, CallExpression, EPtr> implementation;
    public Function<InstantiationSubstitution, ImplementationDataType> instantiateImplementation;
    public BiConsumer<ImplementationDataType, Object> visitImplementationData;
    public Consumer<ImplementationDataType> didLayoutStructsInImplementationData;
    NativeFunc(
            LexerToken origin,
            String name,
            HmType returnType,
            List<? extends Node> typeParameters,
            List<FuncParameter> parameters,
            boolean isCast,
            String shaderType
    ) {
        super(origin, name, returnType, typeParameters.stream().map((typeParameter) -> (Node) typeParameter).collect(Collectors.toList()), parameters, isCast, shaderType);
        this.implementation = null;
        this.instantiateImplementation = (InstantiationSubstitution substitution) -> {
            return new ImplementationDataType(new StructType(origin, "", new ArrayList<Node>()), "");
        };
        this.visitImplementationData = (ImplementationDataType implementationDataType, Object visitor) -> {};
        this.didLayoutStructsInImplementationData = (ImplementationDataType implementationData) -> {};
        this.isRestricted = false;
    }

    @Override
    public boolean isNative() {
        return true;
    }

    @Override
    String toDeclString() {
        return "native " + super.toDeclString();
    }
}

class NativeFuncInstance extends Func {
    private NativeFunc _func1;
    private ImplementationDataType _implementationData;

    NativeFuncInstance(
            NativeFunc func1,
            HmType returnType,
            List<FuncParameter> parameters,
            boolean isCast,
            String shaderType,
            ImplementationDataType implementationData
    ) {
        super(func1.origin, func1.name(), returnType, new ArrayList<>(), parameters, isCast, shaderType);
        this._func1 = func1;
        this._implementationData = implementationData;
    }

    public NativeFunc func1() {
        return this._func1;
    }

    public boolean isNative() {
        return true;
    }

    public ImplementationDataType implementationData() {
        return this._implementationData;
    }

    @Override
    public String toString() {
        return "native " + super.toDeclString();
    }
}

class NativeType extends HmType {
    private boolean _isNumber = false;
    private boolean _isInt = false;
    private boolean _isFloating = false;
    private boolean _isPrimitive = false;
    public boolean isSigned = false;
    public int defaultValue = 0;
    public BiFunction<LexerToken, Integer, GenericLiteral> createLiteral = (origin, value) ->
            new GenericLiteral(origin, value, null);
    public Function<Integer, Integer> successorValue = (value) -> value;
    public BiFunction<Double, Double, Boolean> valuesEqual = (a, b) -> false;
    public BiConsumer<EBuffer, Integer> populateDefaultValue = (buffer, offset) -> {};
    public Function<Integer, Integer> formatValueFromIntLiteral = (value) -> value;
    public Function<Integer, Integer> formatValueFromUintLiteral = (value) -> value;
    public Supplier<List<ResultMemberInfo>> allValues = ArrayList::new;
    public Function<Float, Integer> formatValueFromFloatLiteral = Float::intValue;
    public Function<Double, Integer> formatValueFromDoubleLiteral = Double::intValue;
    public Function<Double, Double> formatValueFrom = (value) -> {
        return  value;
    };

    NativeType(LexerToken origin, String name, List<Node> typeParameters) {
        super();
        if (!(typeParameters instanceof List)) {
            throw new Error("type parameters not array: " + typeParameters);
        }
        this.origin = origin;
        this._isNumber = false;
        this._isInt = false;
        this._isFloating = false;
        this._isPrimitive = false;
        this.name(name);
        this.typeParameters = typeParameters;
    }

    @Override
    public boolean isNative() {
        return true;
    }
    // We let Intrinsics.js set these as it likes.
    @Override
    public boolean isNumber() {
        return this._isNumber;
    }

    public void isNumber(boolean newValue) {
        this._isNumber = newValue;
    }

    @Override
    public boolean isInt() {
        return this._isInt;
    }

    public void isInt(boolean newValue) {
        this._isInt = newValue;
    }

    @Override
    public boolean isFloating() {
        return this._isFloating;
    }

    public void isFloating(boolean newValue) {
        this._isFloating = newValue;
    }

    @Override
    public boolean isPrimitive() {
        return this._isPrimitive;
    }

    public void isPrimitive(boolean newValue) {
        this._isPrimitive = newValue;
    }

    public HmType instantiate(List<TypeVariable> typeArguments) {
        if (typeArguments.size() != this.typeParameters.size()) {
            throw new Error("Wrong number of type arguments to instantiation");
        }
        if (typeArguments.size() == 0) {
            return this;
        }
        return new NativeTypeInstance(this, typeArguments);
    }

    @Override
    public String toString() {
        List<String> arr = this.typeParameters.stream().map((Node typeParameter) -> {
            return typeParameter.toString();
        }).collect(Collectors.toList());
        return "native typedef " + this.name() + "<" + arr.stream().collect(Collectors.joining("")) + ">";
    }
}

class NativeTypeInstance extends HmType {
    NativeTypeInstance(Node type, List<TypeVariable> typeArguments) {
        super();
        this.hmType((HmType) type);
        this.typeArguments = typeArguments.stream().map((data) -> (Node) data).collect(Collectors.toList());
    }

    public boolean isPrimitive() {
        return this._hmType.isPrimitive();
    }

    @Override
    public boolean isNative() {
        return true;
    }

    @Override
    public boolean unifyImpl(UnificationContext unificationContext, Node other) {
        if (this.hmType() != other.hmType()) {
            return false;
        }
        if (this.typeArguments.size() != ((HmType) other).typeArguments.size()) {
            return false;
        }
        for (int i = 0; i < this.typeArguments.size(); i++) {
            if (!this.typeArguments.get(i).unify(unificationContext, ((HmType) other).typeArguments.get(i))) {
                return false;
            }
        }
        return true;
    }

    @Override
    public String toString() {
        return this.hmType() + "<" + this.typeArguments + ">";
    }
}

class NormalUsePropertyResolver extends Rewriter {
    public Expression visitDotExpression2(Node node) {
        return super.visitDotExpression((DotExpression) node).rewriteAfterCloning();
    }

    public Expression visitIndexExpression2(Node node) {
        return super.visitIndexExpression((IndexExpression) node).rewriteAfterCloning();
    }

    @Override
    public Map<String, Function<Base, Object>> mapReturn() {
        Map<String, Function<Base, Object>> tmp = super.mapReturn();
        tmp.put("visitDotExpression", base -> this.visitDotExpression2((Node) base));
        tmp.put("visitIndexExpression", base -> this.visitIndexExpression2((Node) base));
        return tmp;
    }
}

class NullLiteral extends Expression {
    NullLiteral(LexerToken origin) {
        super(origin);
        this.hmType(new NullType(origin));
    }

    @Override
    public String toString() {
        return "null";
    }
}

class NullType extends HmType {
    NullType(LexerToken origin) {
        super();
        this.origin = origin;
    }
    // For now we answer false for isPtr and isArrayRef because that happens to make all of the logic
    // work. But, it's strange, since as a bottom type it really is the case that this is both isPtr and
    // isArrayRef.

    @Override
    public boolean isPrimitive() {
        return true;
    }

    @Override
    public boolean isUnifiable() {
        return true;
    }

    @Override
    public boolean isLiteral() {
        return true;
    }

    public boolean typeVariableUnify(UnificationContext unificationContext, Node other) {
        if (!(other instanceof HmType)) {
            return false;
        }

        return this.typeVariableUnifyImpl(unificationContext, other);
    }

    @Override
    public boolean unifyImpl(UnificationContext unificationContext, Node other) {
        return this.typeVariableUnify(unificationContext, (HmType) other);
    }

    public VerifyResult verifyAsArgument(UnificationContext unificationContext) {
        HmType realThis = (HmType) unificationContext.find(this);
        if (realThis.isPtr() || realThis.isArrayRef()) {
            return new VerifyResult(true);
        }
        return new VerifyResult(false, "Null cannot be used with non-pointer type " + realThis);
    }
    public VerifyResult verifyAsParameter(UnificationContext unificationContext) {
        throw new Error("NullType should never be used as a type parameter");
    }
    public void commitUnification(UnificationContext unificationContext) {
        this.hmType((ReferenceType) unificationContext.find(this));
    }

    @Override
    public String toString() {
        return "nullType";
    }
}

class OverloadResolutionFailure {
    private Func _function;
    private String _reason;

    OverloadResolutionFailure(Func func, String reason) {
        this._function = func;
        this._reason = reason;
    }

    public Func func() {
        return this._function;
    }


    public String reason() {
        return this._reason;
    }


    @Override
    public String toString() {
        return this.func().toDeclString() + " did not match because: " + this.reason();
    }
}

class Program extends Node {
    private FuncInstantiator _funcInstantiator;
    private NameContext _globalNameContext;
    public Intrinsics intrinsics;
    private Map<String, List<Func>> _functions;
    private Map<String, HmType> _types;
    private Map<String, HmProtocol> _protocols;
    private List<Node> _topLevelStatements;

    Program() {
        super();
        this._topLevelStatements = new ArrayList<>();
        this._functions = new HashMap<>();
        this._types = new HashMap<>();
        this._protocols = new HashMap<>();
        this._globalNameContext = new NameContext(null);
        this._globalNameContext.recognizeIntrinsics();
        this.intrinsics = this._globalNameContext.intrinsics();
        this._funcInstantiator = new FuncInstantiator(this);
        this._globalNameContext.program(this);
    }

    public List<Node> topLevelStatements() {
        return _topLevelStatements;
    }

    public Map<String, List<Func>> functions() {
        return this._functions;
    }

    public Map<String, HmType> types() {
        return this._types;
    }

    public Map<String, HmProtocol> protocols() {
        return this._protocols;
    }

    public FuncInstantiator funcInstantiator() {
        return this._funcInstantiator;
    }

    public NameContext globalNameContext() {
        return this._globalNameContext;
    }

    public void add(Node statement) {
        statement.program = this;
        if (statement instanceof Func) {
            List<Func> array = this._functions.get(statement.name()) != null ? this._functions.get(statement.name()) : null ;
            if (array == null) {
                array = new ArrayList<>();
            }
            array.add((Func) statement);
            this._functions.put(statement.name(), array);
        } else if (statement instanceof HmType) {
            this._types.put(statement.name(), (HmType) statement);
        } else if (statement instanceof HmProtocol) {
            this._protocols.put(statement.name(), (HmProtocol) statement);
        } else {
            throw new Error("Statement is not a function or type: " + statement.toString());
        }
        this._topLevelStatements.add(statement);
        this._globalNameContext.add(statement);
    }

    @Override
    public String toString() {
        if (this._topLevelStatements.size() == 0) {
            return "";
        }
        return this._topLevelStatements.stream().map(Node::toString).collect(Collectors.joining(";")) + ";";
    }
}

class PropertyResolver extends Visitor {
    public void visitRValuesWithinLValue(Node node) {
        Function<Node, Node> visit = (nodes) -> {
            return (Node) nodes.visit(this);
        };
        node.visit(new RValueFinder(visit));
    }

    public void visitPropertyAccess(PropertyAccessExpression node) {
        Node newNode = (Node) node.visit(new NormalUsePropertyResolver());
        newNode.visit(this);
        node.become((Expression) newNode);
    }

    public void visitDotExpression(Node node) {
        this.visitPropertyAccess((DotExpression) node);
    }

    public void visitIndexExpression(Node node) {
        this.visitPropertyAccess((IndexExpression) node);
    }

    public void handleReadModifyWrite(ReadModifyWriteExpression node) {
        HmType type = node.oldValueVar.hmType();
        if (type == null) {
            throw new Error("Null type");
        }
        Expression simpleLHS = (Expression) node.lValue().visit(new NormalUsePropertyResolver());
        if (simpleLHS.isLValue()) {
            if (simpleLHS.addressSpace() == null) {
                throw new Error(node.origin.originString() + ": LHS without address space: " + simpleLHS.toString());
            }
            PtrType ptrType = new PtrType(node.origin, simpleLHS.addressSpace(), (TypeRef) type);
            AnonymousVariable ptrVar = new AnonymousVariable(node.origin, ptrType);
            node.become(
                    new CommaExpression(node.origin, List.of(
                    node.oldValueVar,node.newValueVar,ptrVar,
                    new Assignment(node.origin,(Expression)VariableRef.wrap(ptrVar),new MakePtrExpression(node.origin,(HmValue) simpleLHS),ptrType),new Assignment(
                                    node.origin,
                            node.oldValueRef(),
                            new DereferenceExpression(node.origin,(Expression)VariableRef.wrap(ptrVar),(TypeRef)type,simpleLHS.addressSpace()),
                            type
                    ),new Assignment(node.origin,node.newValueRef(),node.newValueExp,type),new Assignment(
                           node.origin,
                           new DereferenceExpression(node.origin,(Expression)VariableRef.wrap(ptrVar),(TypeRef)type, simpleLHS.addressSpace()),
                            node.newValueRef(),
                            type
                    ), node.resultExp)
                    ));
            return;
        }
        ReadModifyWriteExpression result = new ReadModifyWriteExpression(
                node.origin,
                ((PropertyAccessExpression) node.lValue()).base,
                (TypeRef) ((PropertyAccessExpression) node.lValue()).baseType);
        result.newValueExp = new CommaExpression(node.origin,
                List.of(node.oldValueVar, node.newValueVar,
                new Assignment(node.origin, node.oldValueRef(), ((PropertyAccessExpression) node.lValue()).emitGet(result.oldValueRef()), type),
                new Assignment(node.origin, node.newValueRef(), node.newValueExp, type),
                ((PropertyAccessExpression) node.lValue()).emitSet(result.oldValueRef(), node.newValueRef())));
        result.resultExp = node.newValueRef();
        this.handleReadModifyWrite(result);
        node.become(result);
    }

    public void visitReadModifyWriteExpression(Node node) {
        ReadModifyWriteExpression newNode = (ReadModifyWriteExpression) node;
        newNode.newValueExp.visit(this);
        newNode.resultExp.visit(this);
        this.visitRValuesWithinLValue(newNode.lValue());
        this.handleReadModifyWrite(newNode);
    }

    public void visitAssignment(Node node) {
        Assignment newNode = (Assignment) node;
        this.visitRValuesWithinLValue(newNode.lhs());
        newNode.rhs().visit(this);
        Expression simpleLHS = (Expression) newNode.lhs().visit(new NormalUsePropertyResolver());
        if (simpleLHS.isLValue()) {
            newNode.lhs().become(simpleLHS);
            return;
        }
        if (!(newNode.lhs() instanceof PropertyAccessExpression)) {
            throw new Error("Unexpected lhs type: " + newNode.lhs().getClass().getCanonicalName());
        }
        ReadModifyWriteExpression result = new ReadModifyWriteExpression(
                newNode.origin,
                ((PropertyAccessExpression) newNode.lhs()).base,
                (TypeRef) ((PropertyAccessExpression) newNode.lhs()).baseType);
        AnonymousVariable resultVar = new AnonymousVariable(newNode.origin, newNode.hmType());
        result.newValueExp = new CommaExpression(newNode.origin,
                List.of(resultVar, new Assignment(newNode.origin, (Expression) VariableRef.wrap(resultVar), newNode.rhs(), newNode.hmType()),
                ((PropertyAccessExpression) newNode.lhs()).emitSet(result.oldValueRef(), VariableRef.wrap(resultVar))));
        result.resultExp = VariableRef.wrap(resultVar);
        this.handleReadModifyWrite(result);
        newNode.become(result);
    }

    public void visitMakePtrExpression(Node node) {
        MakePtrExpression newNode = (MakePtrExpression) node;
        super.visitMakePtrExpression(newNode);
        if (!newNode.lValue().isLValue()) {
            throw new WTypeError(newNode.origin.originString(), "Not an lvalue: " + newNode.lValue().toString());
        }
    }

    public void visitMakeArrayRefExpression(Node node) {
        MakeArrayRefExpression newNode = (MakeArrayRefExpression) node;
        super.visitMakeArrayRefExpression(node);
        if (!newNode.lValue().isLValue()) {
            throw new WTypeError(newNode.origin.originString(), "Not an lvalue: " + newNode.lValue().toString());
        }
    }
}

class ProtocolFuncDecl extends Func {
    public List<Func> possibleOverloads = new ArrayList<>();

    ProtocolFuncDecl(LexerToken origin, String name, HmType returnType, List<Node> typeParameters, List<FuncParameter> parameters, boolean isCast, String shaderType) {
        super(origin, name, returnType, typeParameters, parameters, isCast, shaderType);
    }

    public List<Node> typeParametersForCallResolution() {
        List<Node> result  = new ArrayList<>(this.typeParameters());
        result.add(this.protocolDecl.typeVariable());
        return result;
    }

}

class ProtocolRef extends HmProtocol {
    public ProtocolDecl protocolDecl;

    ProtocolRef(LexerToken origin, String name) {
        super(origin, name);
        this.protocolDecl = null;
    }

    public VerifyResult inherits(ProtocolRef other) {
        return this.protocolDecl.inherits(other);
    }

    public VerifyResult hasHeir(HmType type) {
        return this.protocolDecl.hasHeir(type);
    }

    @Override
    public boolean isPrimitive() {
        return this.protocolDecl.isPrimitive();
    }
}

class PtrType extends ReferenceType {
    PtrType(LexerToken origin, String addressSpace, HmType elementType) {
        super(origin, addressSpace, elementType);
    }

    @Override
    public boolean isPtr() {
        return true;
    }

    @Override
    public boolean unifyImpl(UnificationContext unificationContext, Node other) {
        HmType newOther = (HmType) other;
        if (!newOther.isPtr()) {
            return false;
        }
        if (!this.addressSpace().equals(newOther.addressSpace())) {
            return false;
        }
        return this.elementType().unify(unificationContext, newOther.elementType());
    }

    @Override
    public HmValue argumentForAndOverload(LexerToken origin, HmValue value) {
        throw new WTypeError(origin.originString(), "Pointer subscript is not valid");
    }

    @Override
    public ReferenceType argumentTypeForAndOverload(LexerToken origin) {
        throw new WTypeError(origin.originString(), "Pointer subscript is not valid");
    }

    public HmType returnTypeFromAndOverload(LexerToken origin) {
        return this.elementType();
    }

    @Override
    public String toString() {
        return (this.elementType() == null ? "" : this.elementType().toString()) + "* " + this.addressSpace();
    }
}

class ReadModifyWriteExpression extends Expression {
    public AnonymousVariable oldValueVar;
    public AnonymousVariable newValueVar;
    public Expression newValueExp;
    public VariableRef resultExp;

    ReadModifyWriteExpression(LexerToken origin, Expression lValue, TypeRef type) {
        super(origin);
        this.lValue(lValue);
        this.oldValueVar = new AnonymousVariable(origin, type);
        this.newValueVar = new AnonymousVariable(origin, type);
        this.newValueExp = null;
        this.resultExp = null;
    }

    public VariableRef oldValueRef() {
        return VariableRef.wrap(this.oldValueVar);
    }

    public VariableRef newValueRef() {
        return VariableRef.wrap(this.newValueVar);
    }

    @Override
    public String toString() {
        if (this.isBecome) {
            return "(" + this.target().toString() + ")";
        } else {
            return "RMW(" + this.lValue().toString() + ", " + this.oldValueVar.toString() + ", " + this.newValueVar.toString() + ", " + this.newValueExp.toString() + ", " + this.resultExp.toString() + ")";
        }
    }
}

class RecursionChecker extends Visitor {
    private VisitingSet _visiting;

    RecursionChecker() {
        super();
        this._visiting = new VisitingSet();
    }

    @Override
    public void visitFuncDef(Node node) {
        this._visiting.doVisit(node, () -> {
            super.visitFuncDef(node);
            return null;
        });
    }

    @Override
    public void visitCallExpression(Node node) {
        ((CallExpression) node).func1.visit(this);
    }
}

class RecursiveTypeChecker extends Visitor {
    private VisitingSet _visiting;

    RecursiveTypeChecker() {
        super();
        this._visiting = new VisitingSet();
    }

    @Override
    public void visitFuncDef(Node node) {}

    @Override
    public void visitNativeFunc(Node node) {}

    @Override
    public void visitStructType(Node node) {
        StructType nodes = (StructType) node;
        this._visiting.doVisit(nodes, () -> {
            super.visitStructType(nodes);
            return null;
        });
    }

    @Override
    public void visitReferenceType(Node node) {}

    @Override
    public void visitTypeRef(Node node) {
        super.visitTypeRef(node);
        node.hmType().visit(this);
    }
}

class Return extends Node {
    private HmValue _value;
    public FuncDef func1;

    Return(LexerToken origin, HmValue value) {
        super();
        this.origin = origin;
        this._value = value;
    }

    public HmValue value() {
        return this._value;

    }

    @Override
    public String toString() {
        if (this.value() != null) {
            return "return " + this.value();
        }
        return "return";
    }
}

class ReturnChecker extends Visitor {
    private Program _program;
    public ReturnStyleType returnStyle;

    ReturnChecker(Program program) {
        super();
        this._program = program;
        this.returnStyle = new ReturnStyleType();
        this.returnStyle.definitelyReturns = "Definitely Returns";
        this.returnStyle.definitelyDoesntReturn = "Definitely Doesn`t Return";
        this.returnStyle.hasntReturnedYet = "Hasn`t Returned Yet";
    }

    private String mergeReturnStyle(String a, String b) {
        if (a == null) {
            return b;
        }
        if (b == null) {
            return a;
        }
        switch (a) {
            case "Definitely Returns":
            case "Definitely Doesn`t Return":
                if (a.equals(b)) {
                    return a;
                }
                return this.returnStyle.hasntReturnedYet;
            case "Hasn`t Returned Yet":
                return this.returnStyle.hasntReturnedYet;
            default:
                throw new Error("Bad return style: " + a);
        }
    }

    public void visitFuncDef(Node node) {
        FuncDef newNode = (FuncDef) node;
        if (newNode.returnType().equals(newNode.program.intrinsics.hmVoid) != null) {
            return;
        }
        String bodyValue = newNode.body().visit(this).toString();
        if (this.returnStyle.definitelyDoesntReturn.equals(bodyValue) || this.returnStyle.hasntReturnedYet.equals(bodyValue)) {
            throw new WTypeError(newNode.origin.originString(), "Function does not return");
        }
    }

    public String visitBlock2(Node node) {
        Block newNode = (Block) node;
        for (Node statement : newNode.statements()) {
            Object obj = statement.visit(this);
            if (obj != null) {
                switch (obj.toString()) {
                    case "Definitely Returns":
                        return this.returnStyle.definitelyReturns;
                    case "Definitely Doesn`t Return":
                        return this.returnStyle.definitelyDoesntReturn;
                    case "Hasn`t Returned Yet":
                        continue;
                    default:
                        break;
                }
            }
        }
        return this.returnStyle.hasntReturnedYet;
    }

    public String visitIfStatement2(Node node) {
        IfStatement newNode = (IfStatement) node;
        if (newNode.elseBody() != null) {
            String bodyValue = (String) newNode.body().visit(this);
            String elseValue = (String) newNode.elseBody().visit(this);
            return this.mergeReturnStyle(bodyValue, elseValue);
        }
        return this.returnStyle.hasntReturnedYet;
    }

    private boolean isBoolCastFromLiteralTrue(CallExpression node) {
        return (
                node.isCast() &&
                        node.returnType() instanceof TypeRef &&
                        node.returnType().equals(this._program.intrinsics.bool) != null &&
                        node.argumentList().size() == 1 &&
                        node.argumentList().get(0) instanceof BoolLiteral &&
                        (boolean) ((BoolLiteral) node.argumentList().get(0)).value()
        );
    }

    public String visitWhileLoop2(Node node) {
        WhileLoop newNode = (WhileLoop) node;
        Object bodyReturn = newNode.body().visit(this);
        if (newNode.conditional() instanceof CallExpression && this.isBoolCastFromLiteralTrue((CallExpression) newNode.conditional())) {
            switch (bodyReturn.toString()) {
                case "Definitely Returns":
                    return this.returnStyle.definitelyReturns;
                case "Definitely Doesn`t Return":
                case "Hasn`t Returned Yet":
                    return this.returnStyle.hasntReturnedYet;
                default:
                    break;
            }
        }
        return this.returnStyle.hasntReturnedYet;
    }

    public String visitDoWhileLoop2(Node node) {
        DoWhileLoop newNode = (DoWhileLoop) node;
        String result = this.returnStyle.hasntReturnedYet;
        switch (newNode.body().visit(this).toString()) {
            case "Definitely Returns":
                return (result = this.returnStyle.definitelyReturns);
            case "Definitely Doesn`t Return":
            case "Hasn`t Returned Yet":
                return (result = this.returnStyle.hasntReturnedYet);
            default:
                break;
        }
        return result;
    }

    public String visitForLoop2(Node node) {
        ForLoop newNode = (ForLoop) node;
        Object bodyReturn = newNode.body().visit(this);
        if (newNode.condition() == null || this.isBoolCastFromLiteralTrue((CallExpression) newNode.condition())) {
            switch ((String) bodyReturn) {
                case "Definitely Returns":
                    return this.returnStyle.definitelyReturns;
                case "Definitely Doesn`t Return":
                case "Hasn`t Returned Yet":
                    return this.returnStyle.hasntReturnedYet;
                default:
                    break;
            }
        }
        return "";
    }

    public String visitSwitchStatement2(Node node) {
        SwitchStatement newNode = (SwitchStatement) node;
        // FIXME: This seems like it's missing things. For example, we need to be smart about this:
        //
        // for (;;) {
        //     switch (...) {
        //     ...
        //         continue; // This continues the for loop
        //     }
        // }
        //
        // I'm not sure what that means for this analysis. I'm starting to think that the right way to
        // build this analysis is to run a visitor that builds a CFG and then analyze the CFG.
        // https://bugs.webkit.org/show_bug.cgi?id=177172

        String returnStyle = null;
        for (SwitchCase switchCase : newNode.switchCases()) {
            String bodyStyle = (String) switchCase.body().visit(this);
            // FIXME: The fact that we need this demonstrates the need for CFG analysis.
            if (bodyStyle == this.returnStyle.definitelyDoesntReturn) {
                bodyStyle = this.returnStyle.hasntReturnedYet;
            }
            returnStyle = this.mergeReturnStyle(returnStyle, bodyStyle);
        }
        return returnStyle;
    }

    public String visitReturn2(Node node) {
        return this.returnStyle.definitelyReturns;
    }

    public String visitTrapStatement2(Node node) {
        return this.returnStyle.definitelyReturns;
    }

    public String visitBreak2(Node node) {
        return this.returnStyle.definitelyDoesntReturn;
    }

    public String visitContinue2(Node node) {
        // FIXME: This seems wrong. Consider a loop like:
        //
        // int foo() { for (;;) { continue; } }
        //
        // This program shouldn't claim that the problem is that it doesn't return.
        // https://bugs.webkit.org/show_bug.cgi?id=177172
        return this.returnStyle.definitelyDoesntReturn;
    }

    public Map<String, Function<Base, Object>> mapReturn() {
        Map<String, Function<Base, Object>> tmp = super.mapReturn();
        tmp.put("visitContinue", node -> this.visitContinue2((Node) node));
        tmp.put("visitBreak", node -> this.visitBreak2((Node) node));
        tmp.put("visitTrapStatement", node -> this.visitTrapStatement2((Node) node));
        tmp.put("visitReturn", node -> this.visitReturn2((Node) node));
        tmp.put("visitSwitchStatement", node -> this.visitSwitchStatement2((Node) node));
        tmp.put("visitForLoop", node -> this.visitForLoop2((Node) node));
        tmp.put("visitDoWhileLoop", node -> this.visitDoWhileLoop2((Node) node));
        tmp.put("visitWhileLoop", node -> this.visitWhileLoop2((Node) node));
        tmp.put("visitIfStatement", node -> this.visitIfStatement2((Node) node));
        tmp.put("visitBlock", node -> this.visitBlock2((Node) node));

        return tmp;
    }
}

class ReturnException extends RuntimeException {
    public EPtr _value;

    ReturnException(EPtr value) {
        super();
        this._value = value;
    }

    public EPtr value() {
        return this._value;
    }
}

class StatementCloner extends Rewriter {
    public FuncDef visitFuncDef(Node node) {
        FuncDef newNode = (FuncDef) node;
        List<Node> typeParameters = newNode.typeParameters().stream().map(typeParameter -> {
            return (Node) typeParameter.visit(this);
        }).collect(Collectors.toList());
        FuncDef result = new FuncDef(
                newNode.origin,
                newNode.name(),
                (HmType) newNode.returnType().visit(this),
                typeParameters,
                newNode.parameters().stream().map(parameter -> {
                    return (FuncParameter) parameter.visit(this);
                }).collect(Collectors.toList()),
                (Block) newNode.body().visit(this),
                newNode.isCast(),
                newNode.shaderType()
        );
        result.isRestricted = newNode.isRestricted;
        return result;
    }

    public NativeFunc visitNativeFunc(Node node) {
        NativeFunc newNode = (NativeFunc) node;
        NativeFunc result = new NativeFunc(
                newNode.origin,
                newNode.name(),
                (HmType) newNode.returnType().visit(this),
                newNode.typeParameters().stream().map(typeParameter -> {
                    return (TypeVariable) typeParameter.visit(this);
                }).collect(Collectors.toList()),
                newNode.parameters().stream().map(parameter -> {
                    return (FuncParameter) parameter.visit(this);
                }).collect(Collectors.toList()),
                newNode.isCast(),
                newNode.shaderType()
        );
        result.isRestricted = newNode.isRestricted;
        return result;
    }

    public NativeType visitNativeType(Node node) {
        NativeType newNode = (NativeType) node;
        return new NativeType(
                newNode.origin,
                newNode.name(),
                newNode.typeParameters.stream().map(typeParameter -> {
                    return (Node) typeParameter.visit(this);
                }).collect(Collectors.toList())
        );
    }

    public TypeDef visitTypeDef(Node node) {
        TypeDef newNode = (TypeDef) node;
        return new TypeDef(
                newNode.origin,
                newNode.name(),
                newNode.typeParameters.stream().map(typeParameter -> {
                    return (Node) typeParameter.visit(this);
                }).collect(Collectors.toList()),
                (TypeRef) newNode.hmType().visit(this)
        );
    }

    public StructType visitStructType(Node node) {
        StructType newNode = (StructType) node;
        StructType result = new StructType(
                newNode.origin,
                newNode.name(),
                newNode.typeParameters.stream().map(typeParameter -> {
                    return (TypeVariable) typeParameter.visit(this);
                }).collect(Collectors.toList())
        );
        for (Field field : newNode.fields()) {
            result.add((Field) field.visit(this));
        }
        return result;
    }

    public ConstexprTypeParameter visitConstexprTypeParameter(Node node) {
        ConstexprTypeParameter newNode = (ConstexprTypeParameter) node;
        return new ConstexprTypeParameter(newNode.origin, newNode.name(), (TypeRef) newNode.hmType().visit(this));
    }

    public ProtocolDecl visitProtocolDecl(Node node) {
        ProtocolDecl newNode = (ProtocolDecl) node;
        ProtocolDecl result = new ProtocolDecl(newNode.origin, newNode.name());
        for (ProtocolRef hmProtocol : newNode.extend) {
            result.addExtends((ProtocolRef) hmProtocol.visit(this));
        }
        for (ProtocolFuncDecl signature : newNode.signatures()) {
            result.add((ProtocolFuncDecl) signature.visit(this));
        }
        return result;
    }

    public TypeVariable visitTypeVariable(Node node) {
        TypeVariable newNode = (TypeVariable) node;
        return new TypeVariable(newNode.origin, newNode.name(), (ProtocolRef) Node.visit(newNode.protocol1(), this));
    }

    public ProtocolRef visitProtocolRef(Node node) {
        ProtocolRef newNode = (ProtocolRef) node;
        return new ProtocolRef(newNode.origin, newNode.name());
    }

    public BoolLiteral visitBoolLiteral(Node node) {
        BoolLiteral newNode = (BoolLiteral) node;
        return new BoolLiteral(newNode.origin, (boolean) newNode.value());
    }

    public TypeOrVariableRef visitTypeOrVariableRef(Node node) {
        TypeOrVariableRef newNode = (TypeOrVariableRef) node;
        return new TypeOrVariableRef(newNode.origin, newNode.name());
    }

    public EnumType visitEnumType(Node node) {
        EnumType newNode = (EnumType) node;
        EnumType result = new EnumType(newNode.origin, newNode.name(), (HmType) newNode.baseType().visit(this));
        for (EnumMember member : newNode.members()) {
            result.add(member);
        }
        return result;
    }
}

class StructLayoutBuilder extends Visitor {
    private int _offset;

    StructLayoutBuilder() {
        super();
        this._offset = 0;
    }

    public void visitReferenceType(Node node) {
    }

    public void visitStructType(Node node) {
        StructType nodes = (StructType) node;
        if (nodes.size() != -1) {
            return;
        }
        if (nodes.typeParameters.size() != 0) {
            throw new Error("Cannot do layout for generic type: " + nodes.toString());
        }
        int oldOffset = this._offset;
        this._offset = 0;
        super.visitStructType(nodes);
        nodes.size(this._offset);
        this._offset += oldOffset;
    }

    public int offset() {
        return this._offset;
    }

    public void visitField(Node node) {
        Field newNode = (Field) node;
        super.visitField(newNode);
        newNode.offset = this._offset;
        int size = newNode.hmType().size();
        if (size == -1) {
            throw new Error("Type does not have size: " + newNode.hmType().toString());
        }
        this._offset += size;
    }

    public void visitNativeFuncInstance(Node node) {
        NativeFuncInstance newNode = (NativeFuncInstance) node;
        super.visitNativeFuncInstance(newNode);
        newNode.func1().didLayoutStructsInImplementationData.accept(newNode.implementationData());
    }

    public void visitTypeRef(Node node) {
        super.visitTypeRef(node);
        node.hmType().visit(this);
    }

    public void visitCallExpression(Node node) {
        CallExpression newNode = (CallExpression) node;
        for (HmValue argument : newNode.argumentList()) {
            Node.visit(argument, this);
        }
        Consumer<List<Node>> handleTypeArguments = (List<Node> actualTypeArguments) -> {
            if (actualTypeArguments != null) {
                for (Node argument : actualTypeArguments) {
                    argument.visit(this);
                }
            }
        };
        handleTypeArguments.accept(newNode.instantiatedActualTypeArguments);
        Node.visit(newNode.nativeFuncInstance, this);
        Node.visit(newNode.resultType, this);
    }
}

class StructType extends HmType {
    private Map<String, Field> _fields;

    public List<Field> fields() {
        List<Field> tmp = new ArrayList<>();
        for (Field ele : this._fields.values()) {
            tmp.add(ele);
        }
        return tmp;
    }

    public boolean isPrimitive() {
        int result = 1;
        for (Field field : this.fields()) {
            result = result & (field.hmType().isPrimitive() ? 1 : 0);
        }
        return result == 1;
    }

    StructType(LexerToken origin, String name, List<Node> typeParameters) {
        super();
        this.origin = origin;
        this._fields = new HashMap<String, Field>();
        this.name(name);
        this.typeParameters = typeParameters;
    }

    public void add(Field field) {
        field.setStruct1(this);
        List<String> names = new ArrayList<>();
        for (String ele : this._fields.keySet()) {
            names.add(ele);
        }
        if (names.contains(field.name())) {
            throw new WTypeError(field.origin.originString(), "Duplicate field name: " + field.name());
        }
        this._fields.put(field.name(), field);
    }

    public Field fieldByName(String name) {
        return this._fields.get(name);
    }

    public StructType instantiate(List<Node> typeArguments) {
        Substitution substitution = null;
        List<Node> typeParameters = this.typeParameters;
        if (typeArguments != null) {
            if ((typeArguments.size() > 0 ? typeArguments.size() : 0) != this.typeParameters.size()) {
                throw new WTypeError(this.origin.originString(), "Wrong number of type arguments to instantiation");
            }
            substitution = new Substitution(this.typeParameters, typeArguments);
            typeParameters = new ArrayList<>();
        }
        InstantiateImmediates instantiateImmediates = new InstantiateImmediates();
        StructType result = new StructType(this.origin, this.name(), typeParameters);
        for (Field field : this.fields()) {
            Field newField = field;
            if (substitution != null) {
                newField = (Field) newField.visit(substitution);
            }
            newField = (Field) newField.visit(instantiateImmediates);
            result.add(newField);
        }
        return result;
    }

    public TypeRef populateDefaultValue(EBuffer buffer, int offset) {
        if (this.size() == -1) {
            throw new Error("Struct does not have layout: " + this.toString() + " " + this);
        }
        for (Field field : this.fields()) {
            field.hmType().populateDefaultValue(buffer, offset + field.offset);
        }
        return null;
    }

    public String toString() {
        return "struct " + this.name() + "<" + this.typeParameters + "> { " + this.fields().stream().map(Node::toString).collect(Collectors.joining(";")) + "; }";
    }
}

class Substitution extends Rewriter {
    private Map<Node, Node> _map;

    Substitution(List<Node> parameters, List<Node> argumentList) {
        super();
        this._map = new HashMap<>();
        if (parameters.size() != argumentList.size()) {
            throw new Error("Parameters and arguments are mismatched");
        }
        for (int i = 0; i < parameters.size(); i++) {
            this._map.put(parameters.get(i), argumentList.get(i));
        }
    }

    public Map<Node, Node> map() {
        return this._map;
    }

    public HmType visitTypeRef2(Node node) {
        TypeRef newNode = (TypeRef) node;
        Node replacement = this._map.get(((TypeRef) newNode).hmType());
        if (replacement != null) {
            if (newNode.typeArguments.size() > 0) {
                throw new Error("Unexpected type arguments on type variable");
            }
            HmType result = (HmType) replacement.visit(new AutoWrapper());
            return result;
        }
        TypeRef result = super.visitTypeRef(newNode);
        return result;
    }

    public Expression visitVariableRef2(Node node) {
        VariableRef newNode = (VariableRef) node;
        Node replacement = this._map.get(newNode.variable);
        if (replacement != null) {
            return (Expression) replacement.visit(new AutoWrapper());
        }
        return super.visitVariableRef(newNode);
    }

    public Map<String, Function<Base, Object>> mapReturn() {
        Map<String, Function<Base, Object>> tmp = super.mapReturn();
        tmp.put("visitTypeRef", node -> this.visitTypeRef2((Node) node));
        tmp.put("visitVariableRef", node -> this.visitVariableRef2((Node) node));
        return tmp;
    }
}

class SwitchCase extends Node {
    private Expression _value;

    SwitchCase(LexerToken origin, Expression value, Block body) {
        super();
        this.origin = origin;
        this._value = value;
        this.body(body);
    }

    public boolean isDefault() {
        return this._value == null;
    }

    public Expression value() {
        return this._value;
    }

    public String toString() {
        String result = "";
        if (this.isDefault()) {
            result += "default";
        } else {
            result += "cast " + this.value().toString();
        }
        return result + ": " + this.body().toString();
    }
}

class SwitchStatement extends Node {
    private HmValue _value;
    private List<SwitchCase> _switchCases;

    SwitchStatement(LexerToken origin, HmValue value) {
        super();
        this.origin = origin;
        this._value = value;
        this._switchCases = new ArrayList<>();
    }

    public HmValue value() {
        return this._value;
    }

    public void add(SwitchCase switchCase) {
        this._switchCases.add(switchCase);
    }

    public List<SwitchCase> switchCases() {
        return this._switchCases;
    }

    public String toString() {
        String result = "switch (" + this.value().toString() + ") { ";
        if (this.switchCases().size() != 0) {
            result += this.switchCases().stream().map(Node::toString).collect(Collectors.joining(";")) + "; ";
        }
        return result + "}";
    }
}

class TrapStatement extends Node {
    TrapStatement(LexerToken origin) {
        super();
        this.origin = origin;
    }

    public String toString() {
        return "trap";
    }
}

class TypeDef extends HmType {
    TypeDef(LexerToken origin, String name, List<Node> typeParameters, HmType type) {
        super();
        this.origin = origin;
        this.hmType(type);
        this.name(name);
        this.typeParameters = typeParameters;
    }

    public String toString() {
        return "typedef " + this.name() + "<" + this.typeParameters.stream().map(Node::toString).collect(Collectors.joining(",")) + "> = " + this.hmType().toString();
    }
}

class TypeDefResolver extends Visitor {
    private VisitingSet _visiting;

    TypeDefResolver() {
        super();
        this._visiting = new VisitingSet();
    }

    public void visitTypeRef(Node node) {
        TypeRef newNode = (TypeRef) node;
        this._visiting.doVisit(newNode, () -> {
            for (Node typeArgument : newNode.typeArguments) {
                typeArgument.visit(this);
            }
            if (newNode.hmType() instanceof TypeDef) {
                UnificationContext unificationContext = new UnificationContext(newNode.hmType().typeParameters);
                if (newNode.typeArguments.size() != newNode.hmType().typeParameters.size()) {
                    throw new Error("argument/parameter mismatch (should have been caught earlier)");
                }
                for (int i = 0; i < newNode.typeArguments.size(); i++) {
                    newNode.typeArguments.get(i).unify(unificationContext, newNode.hmType().typeParameters.get(i));
                }
                VerifyResult verificationResult = unificationContext.verify();
                if (!verificationResult.result) {
                    throw new WTypeError(
                            newNode.origin.originString(),
                            "Type reference to a type definition violates protocol constraints: " + verificationResult.reason
                    );
                }
                HmType newType = newNode.hmType().hmType().substituteToUnification(newNode.hmType().typeParameters, unificationContext);
                newType.visit(this);
                newNode.setTypeAndArguments(newType, new ArrayList<>());
            }
            return null;
        });
    }
}

class TypeOrVariableRef extends Node {
    TypeOrVariableRef(LexerToken origin, String name) {
        super();
        this.name(name);
        this.origin = origin;
    }

    public String toString() {
        return this.name();
    }
}

class TypeParameterRewriter extends Base {
    public ConstexprTypeParameter visitConstexprTypeParameter(Base aNode) {
        ConstexprTypeParameter node = (ConstexprTypeParameter) aNode;
        return new ConstexprTypeParameter(node.origin, node.name(), (TypeRef) node.hmType().visit(new Rewriter()));
    }

    public TypeVariable visitTypeVariable(Base aNode) {
        TypeVariable node = (TypeVariable) aNode;
        return new TypeVariable(node.origin, node.name(), node.protocol1());
    }

    public Map<String, Function<Base, Object>> mapReturn() {
        Map<String, Function<Base, Object>> tmp = super.mapReturn();
        tmp.put("visitConstexprTypeParameter", aNode -> this.visitConstexprTypeParameter((Base) aNode));
        tmp.put("visitTypeVariable", aNode -> this.visitTypeVariable((Base) aNode));
        return tmp;
    }
}

class TypeRef extends HmType {
    TypeRef(LexerToken origin, String name, List<Node> typeArguments) {
        super();
        this.origin = origin;
        this.name(name);
        this.hmType(null);
        this.typeArguments = typeArguments;
    }

    public static TypeRef wrap(HmType type) {
        if (type instanceof TypeRef && type.typeArguments.size() == 0) {
            return (TypeRef) type;
        }
        String name = type.name();
        TypeRef result = new TypeRef(type.origin, name, new ArrayList<>());
        result.hmType(type);
        return result;
    }

    public static TypeRef instantiate(StructType type, List<HmType> typeArguments) {
        TypeRef result = new TypeRef(type.origin, type.name(), typeArguments.stream().map((typeArgument) -> (Node) typeArgument).collect(Collectors.toList()));
        result.hmType(type);
        return result;
    }

    public Node unifyNode() {
        if (this.typeArguments.size() == 0) {
            return this.hmType().unifyNode();
        }
        return this;
    }

    public TypeRef populateDefaultValue(EBuffer buffer, int offset) {
        if (this.typeArguments.size() == 0) {
            return (TypeRef) this.hmType().populateDefaultValue(buffer, offset);
        }
        throw new Error("Cannot get default value of a type instantiation");
    }

    public int size() {
        if (this.typeArguments.size() == 0) {
            return this.hmType().size();
        }
        throw new Error("Cannot get size of a type instantiation");
    }

    public boolean isPrimitive() {
        if (this.typeArguments.size() == 0) {
            return this.hmType() != null ? this.hmType().isPrimitive() : false;
        }
        throw new Error("Cannot determine if an uninstantiated type is primitive: " + this);
    }

    public void setTypeAndArguments(HmType type, List<Node> typeArguments) {
        this.name("");
        this.hmType(type);
        this.typeArguments = typeArguments;
    }

    public boolean unifyImpl(UnificationContext unificationContext, Node other) {
        if (!(other instanceof TypeRef)) {
            return false;
        }
        if (!this.hmType().unify(unificationContext, ((TypeRef) other).hmType())) {
            return false;
        }
        if (this.typeArguments.size() != ((TypeRef) other).typeArguments.size()) {
            return false;
        }
        for (int i = 0; i < this.typeArguments.size(); i++) {
            if (!this.typeArguments.get(i).unify(unificationContext, ((TypeRef) other).typeArguments.get(i))) {
                return false;
            }
        }
        return true;
    }

    public String toString() {
        if (this.name().equals("")) {
            return this.hmType().toString();
        }
        if (this.typeArguments.size() == 0) {
            return this.name();
        }
        return this.name() + "<" + this.typeArguments.stream().map(Node::toString).collect(Collectors.joining(",")) + ">";
    }
}

class TypeVariable extends HmType {
    private ProtocolRef _protocol1;

    TypeVariable(LexerToken origin, String name, ProtocolRef protocol1) {
        super();
        this.origin = origin;
        this._protocol1 = protocol1;
        name(name);
    }

    public ProtocolRef protocol1() {
        return this._protocol1;
    }

    public boolean isPrimitive() {
        return this._protocol1 != null && this._protocol1.isPrimitive();
    }

    public boolean isUnifiable() {
        return true;
    }

    public VerifyResult inherits(ProtocolRef protocol1) {
        if (protocol1 == null) {
            return new VerifyResult(true);
        }
        if (this.protocol1() == null) {
            return new VerifyResult(false, "Type variable " + this.toString() + " does not have a protocol");
        }
        return this.protocol1().inherits(protocol1);
    }

    public boolean typeVariableUnify(UnificationContext unificationContext, Node other) {
        if (!(other instanceof HmType)) {
            return false;
        }
        return this.typeVariableUnifyImpl(unificationContext, other);
    }

    public boolean unifyImpl(UnificationContext unificationContext, Node other) {
        return this.typeVariableUnify(unificationContext, other);
    }

    public VerifyResult verifyAsArgument(UnificationContext unificationContext) {
        Node realThis = unificationContext.find(this);

        // The thing we get unified with must be a type variable that accepts a broader set of
        // things than we do.
        if (!(realThis instanceof TypeVariable)) {
            return new VerifyResult(false, "Type variable argument " + this.toString() + " cannot be passed to non-type-variable parameter type " + realThis.toString());
        }
        TypeVariable real = (TypeVariable) realThis;
        if (this.protocol1() == null) {
            if (real.protocol1() != null) {
                return new VerifyResult(
                        false,
                        "Type variable without protocol " + this.toString() + " cannot be passed to parameter type variable with protocol " + real.protocol1().toString()
                );
            }
            return new VerifyResult(true);
        }
        VerifyResult result = this.protocol1().inherits(real.protocol1());
        if (!result.result) {
            return new VerifyResult(
                    false,
                    "Protocol " + this.protocol1().toString() + " does not subsume protocol " + real.protocol1().toString() + " (passing type " + this.toString() + " to type " + real.toString() + "): " +
                            result.reason
            );
        }
        return new VerifyResult(true);
    }

    public VerifyResult verifyAsParameter(UnificationContext unificationContext) {
        if (this.protocol1() == null) {
            return new VerifyResult(true);
        }
        HmType realThis = (HmType) unificationContext.find(this);
        VerifyResult result = realThis.inherits(this.protocol1());
        if (!result.result) {
            return new VerifyResult(
                    false,
                    "Type " + realThis.toString() + " does not inherit protocol " + this.protocol1().toString() + " (passing type " + realThis.toString() + " to type " + this.toString() + "): " +
                            result.reason
            );
        }
        return new VerifyResult(true);
    }

    public String toString() {
        return this.name() + (this.protocol1() != null ? ":" + this.protocol1().name() : "");
    }

}

class TypeVariableTracker extends Visitor {
    private Set<Node> _set;
    TypeVariableTracker() {
        super();
        this._set = new HashSet<>();
    }

    public Set<Node> set() {
        return this._set;
    }

    public void consider(Node thing) {
        if (thing.isUnifiable()) {
            this._set.add(thing);
        }
    }

    public void visitTypeRef(Node node) {
        TypeRef newNode = (TypeRef) node;
        if (newNode.typeArguments.size() != 0) {
            for (Node typeArgument : newNode.typeArguments) {
                typeArgument.visit(this);
            }
            return;
        }
        this.consider(newNode.hmType());
    }

    public void visitVariableRef(Node node) {
        this.consider(((VariableRef) node).variable);
    }
}

class TypedValue {
    private HmType _type;
    private EPtr _ePtr;

    TypedValue(HmType type, EPtr ePtr) {
        this._type = type;
        this._ePtr = ePtr;
    }

    public HmType hmType() {
        return this._type;
    }

    public EPtr ePtr() {
        return this._ePtr;
    }

    public static TypedValue box(HmType type, Object value) {
        return new TypedValue(type, EPtr.boxStatic(value));
    }

    public Object value() {
        return this.ePtr().loadValue();
    }

    public String toString() {
        return this.hmType().toString() + "(" + this.ePtr() != null ? this.ePtr().toString() : "null" + ")";
    }
}

class UnificationContext {
    public Set<Node> _typeParameters;
    public Map<Node, Node> _nextMap;
    public Set<Node> _extraNodes;

    UnificationContext(List<Node> typeParameters) {
        this._typeParameters = new HashSet<>();
        this._typeParameters.addAll(typeParameters);
        this._nextMap = new HashMap<>();
        this._extraNodes = new HashSet();
    }

    public void union(Node a, Node b) {
        a = this.find(a);
        b = this.find(b);
        if (a == b) {
            return;
        }
        if (!a.isUnifiable()) {
            Node tmp = b;
            b = a;
            a = tmp;
            if (!a.isUnifiable()) {
                throw new Error(String.format("Cannot unify non-unifiable things %s} and %s", a.toString(), b.toString()));
            }
        }

        // Make sure that type parameters don't end up being roots.
        if (a.isUnifiable() && b.isUnifiable() && this._typeParameters.contains(b)) {
            Node tmp = b;
            b = a;
            a = tmp;
        }
        this._nextMap.put(a, b);
    }

    public Node find(Node node) {
        Node currentNode = node;
        Node nextNode = this._nextMap.get(currentNode);
        if (nextNode == null) {
            return currentNode;
        }
        while (true) {
            currentNode = nextNode;
            nextNode = this._nextMap.get(currentNode);
            if (nextNode == null) {
                break;
            }
        }
        this._nextMap.put(node, currentNode);
        return currentNode;
    }

    public void addExtraNode(Node node) {
        this._extraNodes.add(node);
    }

    public Set<Node> nodes() {
        Set<Node> result = new HashSet<>();
        for (Node item : this._nextMap.keySet()) {
            result.add(item);
            result.add(_nextMap.get(item));
        }
        for (Node node : this._extraNodes) {
            result.add(node);
        }
        return result;
    }

    public Set<Node> typeParameters() {
        return this._typeParameters;
    }

    public ArrayList<Node> typeArguments() {
        ArrayList<Node> tmpNodes = new ArrayList<>();
        for (Node typeArgument : this.nodes()) {
            if (!typeArgument.isUnifiable()) {
                continue;
            }
            if (this._typeParameters.contains(typeArgument)) {
                continue;
            }
            tmpNodes.add(typeArgument);
        }
        return tmpNodes;
    }

    public VerifyResult verify() {
        // We do a two-phase pre-verification. This gives literals a chance to select a more specific type.
        ArrayList<Supplier<VerifyResult>> preparations = new ArrayList<>();
        for (Node node : this.nodes()) {
            Supplier<VerifyResult> preparation = null;
            if (node instanceof GenericLiteralType) {
                preparation = ((GenericLiteralType) node).prepareToVerify(this);
            }
            if (preparation != null) {
                preparations.add(preparation);
            }
        }
        for (Supplier<VerifyResult> preparation : preparations) {
            VerifyResult result = preparation.get();
            if (!result.result) {
                return result;
            }
        }
        for (Node typeParameter : this._typeParameters) {
            VerifyResult result = typeParameter.verifyAsParameter(this);
            if (!result.result) {
                return result;
            }
        }
        int numTypeVariableArguments = 0;
        Set<Node> argumentSet = new HashSet<>();
        for (Node typeArgument : this.typeArguments()) {
            VerifyResult result = typeArgument.verifyAsArgument(this);
            if (!result.result) {
                return result;
            }
            if (typeArgument.isLiteral()) {
                continue;
            }
            argumentSet.add(this.find(typeArgument));
            numTypeVariableArguments += 1;
        }
        if (argumentSet.size() == numTypeVariableArguments) {
            return new VerifyResult(true);
        }
        return new VerifyResult(false, "Type variables used as arguments got unified with each other");
    }

    int conversionCost() {
        int result = 0;
        for (Node typeArgument : this.typeArguments()) {
            result += typeArgument.conversionCost(this);
        }
        return result;
    }

    public void commit() {
        for (Node typeArgument : this.typeArguments()) {
            typeArgument.commitUnification(this);
        }
    }


}

class UnreachableCodeChecker extends Visitor {
    private ReturnChecker _returnChecker;

    UnreachableCodeChecker(Program program) {
        super();
        this._returnChecker = new ReturnChecker(program);
    }

    public void visitBlock(Node node) {
        Block newNode = (Block) node;
        super.visitBlock(newNode);
        if (newNode.statements().size() == 0) {
            return;
        }
        for (int i = 0; i < newNode.statements().size() - 1; ++i) {
            switch ((String) (newNode.statements().get(i).visit(this._returnChecker) != null ? newNode.statements().get(i).visit(this._returnChecker) : "")) {
                case "Definitely Returns":
                case "Definitely Doesn`t Return":
                    throw new WTypeError(newNode.statements().get(i + 1).origin.originString(), "Unreachable code");
                case "Hasn`t Returned Yet":
                    continue;
                default:
                    break;
            }
        }
    }
}

class VariableDecl extends HmValue {
    private HmValue _initializer;

    VariableDecl(LexerToken origin, String name, Node type, HmValue initializer) {
        super();
        this.origin = origin;
        this.name(name);
        this.hmType((HmType) type);
        this._initializer = initializer;
    }

    public HmValue initializer() {
        return this._initializer;
    }

    public boolean varIsLValue() {
        return true;
    }

    public String toString() {
        return  (this.hmType().toString() != null ? this.hmType().toString() : "") + this.name() + (this.initializer() != null ? (this.initializer().toString() != null ? "=" + this.initializer().toString() : "") : "" );
    }

}

class VariableRef extends Expression {
    public HmValue variable;

    VariableRef(LexerToken origin, String name) {
        super(origin);
        this.name(name);
        this.variable = null;
        this.addressSpace("thread");
    }

    static VariableRef wrap(HmValue variable) {
        VariableRef result = new VariableRef(variable.origin, variable.name());
        result.variable = variable;
        return result;
    }

    public boolean isConstexpr() {
        if (this.isBecome) {
            return this.target().isConstexpr();
        } else {
            return this.variable.isConstexpr();
        }
    }

    public Node unifyNode() {
        // This only makes sense when this is a constexpr.
        if (this.isBecome) {
            return this.target().unifyNode();
        } else {
            return (ConstexprTypeParameter) this.variable.unifyNode();
        }
    }

    public boolean isLValue() {
        if (this.isBecome) {
            return this.target().isLValue();
        } else {
            if (this.variable instanceof FuncParameter) {
                return ((FuncParameter) this.variable).varIsLValue();
            }
            if (this.variable instanceof VariableDecl) {
                return ((VariableDecl) this.variable).varIsLValue();
            }
            return false;
        }
    }

    @Override
    public String addressSpace() {
        if (this.isBecome) {
            return this.target().addressSpace();
        } else {
            return "thread";
        }
    }

    public String toString() {
        if (this.isBecome) {
            return "(" + this.target().toString() + ")";
        } else {
            return this.name();
        }
    }
}

class VisitingSet {
    private Set<Node> _set;

    VisitingSet() {
        this._set = new HashSet<>(new ArrayList<>());
    }

    VisitingSet(List<Node> items) {
        this._set = new HashSet<>(items);
    }

    public <T> T doVisit(Node item, Supplier<T> callback) {
        if (this._set.contains(item)) {
            throw new WTypeError(item.origin.originString(), "Recursive " + item.kind());
        }
        this._set.add(item);
        try {
            return callback.get();
        } finally {
            this._set.remove(item);
        }
    }
}

class NormalError extends RuntimeException {
    public String message;

    NormalError(String message) {
        super();
        this.message = message;
    }
}

class WSyntaxError extends NormalError {
    public String originString;
    public String syntaxErrorMessage;
    WSyntaxError(String originString, String message) {
        super("Syntax error at " + originString + ":" + message);
        this.originString = originString;
        this.syntaxErrorMessage = message;
    }
}

class WTrapError extends NormalError {
    public String originString;
    public String syntaxErrorMessage;

    WTrapError(String originString, String message) {
        super("Trap at " + originString + ": " + message);
        this.originString = originString;
        this.syntaxErrorMessage = message;
    }
}

class WTypeError extends NormalError {
    public String originString;
    public String typeErrorMessage;

    WTypeError(String originString) {
        super("Type error at " + originString);
        this.originString = originString;
    }

    WTypeError(String originString, String message) {
        super("Type error at " + originString + ":" + message);
        this.originString = originString;
        this.typeErrorMessage = message;
    }

}

class WrapChecker extends Visitor {
    private Node _startNode;

    WrapChecker(Node node) {
        super();
        this._startNode = node;
    }

    public void visitVariableRef(Node node) {}

    public void visitTypeRef(Node node) {}

    public String originString(Node node) {
        LexerToken origin = node.origin;
        if (origin == null) {
            return "<null origin>";
        }
        return origin.originString();
    }

    public void foundUnwrapped(Node node) {
        String classStr = node.getClass().getSimpleName();
        String classStr1 = this._startNode.getClass().getSimpleName();
        throw new Error(
                "Found unwrapped" + classStr + " at " + this.originString(node) + ":" + node.toString() + "\\nWhile visiting " + classStr1 + " at " + this.originString(
                        this._startNode) +
                        ": " + this._startNode.toString());
    }

    public void visitConstexprTypeParameter(Node node) {
        this.foundUnwrapped(node);
    }

    public void visitFuncParameter(Node node) {
        this.foundUnwrapped(node);
    }

    public void visitVariableDecl(Node node) {
        this.foundUnwrapped(node);
    }

    public void visitStructType(Node node) {
        this.foundUnwrapped(node);
    }

    public void visitNativeType(Node node) {
        this.foundUnwrapped(node);
    }

    public void visitTypeVariable(Node node) {
        this.foundUnwrapped(node);
    }

    // NOTE: This does not know how to handle NativeTypeInstance, because this is never called on instantiated
    // code. Once code is instantiated, you cannot instantiate it further.

    // NOTE: This needs to be kept in sync with AutoWrapper.

}

class GenericLiteralType extends HmType {
    private double _value;
    public TypeRef preferredType;
    public ConfigType _config;

    GenericLiteralType(LexerToken origin, double value, ConfigType config) {
        super();
        this._value = value;
        this._config = config;
        this.preferredType = new TypeRef(origin, config.preferredTypeName, new ArrayList<>());
        this.origin = origin;
    }

    public double value() {
        return this._value;
    }

    public boolean isUnifiable() {
        return true;
    }

    @Override
    public boolean isLiteral() {
        return true;
    }

    public boolean typeVariableUnify(UnificationContext unificationContext, Node other) {
        if (other instanceof HmType == false) {
            return false;
        }
        return this.typeVariableUnifyImpl(unificationContext, other);
    }

    public boolean unifyImpl(UnificationContext unificationContext, Node other) {
        return this.typeVariableUnify(unificationContext, (HmType) other);
    }

    @Override
    public Supplier<VerifyResult> prepareToVerify(UnificationContext unificationContext) {
        Node realThis = unificationContext.find(this);
        if (realThis instanceof TypeVariable || realThis.isLiteral()) {
            return () -> {
                if (realThis.unify(unificationContext, this.preferredType)) {
                    return new VerifyResult(true);
                }
                return new VerifyResult(false, "Type mismatch between " + unificationContext.find(realThis) + " and " + this.preferredType);
            };
        }
        return null;
    }

    public VerifyResult verifyAsArgument(UnificationContext unificationContext) {
        return this._config.verifyAsArgument.apply(unificationContext, this);
    }

    public VerifyResult verifyAsParameter(UnificationContext unificationContext) {
        throw new Error("GenericLiteralType should never be used as a type parameter");
    }

    public int conversionCost(UnificationContext unificationContext) {
        NativeType realThis = (NativeType) unificationContext.find(this);
        if (realThis.equals(this.preferredType) != null) {
            return 0;
        }
        return 1;
    }

    public void commitUnification(UnificationContext unificationContext) {
        this.hmType((TypeRef) unificationContext.find(this).visit(new AutoWrapper()));
    }

    public String toString() {
        return this._config.preferredTypeName + "LiteralType<" + this.value() + ">";
    }

}

class LexerTokenExternal extends LexerToken {
    LexerTokenExternal(Lexer lexer, int index, String kind, String text) {
        super(lexer, 0, "", "");
    }
}

class GenericLiteral extends Expression {
    public String literalClassName = "";
    public String preferredTypeName = "";
    public Config _config;

    GenericLiteral(LexerToken origin, double value, Config config) {
        super(origin);
        this._value = value;
        this._config = config;
        this.origin = origin;
        if (config != null) {
            this.hmType(config.createType.apply(origin, value));
        }
    }

    public static GenericLiteral withType(LexerToken origin, double value, NativeType type) {
        GenericLiteral result = new GenericLiteral(origin, value, null);
        result.hmType(TypeRef.wrap(type));
        return result;
    }

    @Override
    public Object value() {
        return this._value;
    }

    // This is necessary because once we support int64, we'll need that to be represented as an object
    // rather than as a primitive. Then we'll need to convert.
    public double valueForSelectedType() {
        Node type = this.hmType().hmType().unifyNode();
        if (type == null) {
            throw new Error("Cannot get type for " + this);
        }
        Function<Double, Double> func1 = ((NativeType) type).formatValueFrom;
        if (func1 == null) {
            throw new Error("Cannot get function to format type for " + this.literalClassName + " from " + type.toString());
        }
        return func1.apply((double) this.value());
    }

    public boolean isConstexpr() {
        return true;
    }

    @Override
    public boolean isLiteral() {
        return true;
    }

    public GenericLiteral negConstexpr() {
        {
            if (this._config == null || this._config.negConstexpr == null) {
                return null;
            }
            return _config.negConstexpr.apply(origin, (Double) value());
        }
    }

    public boolean unifyImpl(UnificationContext unificationContext, Node other) {
        if (!(other instanceof GenericLiteral)) {
            return false;
        }
        return ((double) this.value()) == ((double) ((GenericLiteral) other).value());
    }

    public String toString() {
        String preferredTypeName = this._config != null ? this._config.preferredTypeName : null;
        String literalType = preferredTypeName != null ? preferredTypeName : "null";
        return literalType + "Literal<" + this.value() + ">";
    }

}

class HmSymbol { }

class WslSymbol extends Error { }

class Config {
    public String literalClassName;
    public String preferredTypeName;
    public BiFunction<LexerToken, Double, GenericLiteral> negConstexpr;
    public BiFunction<LexerToken, Double, GenericLiteralType> createType;
    Config(
            String literalClassName,
            String preferredTypeName,
            BiFunction<LexerToken, Double, GenericLiteral> negConstexpr,
            BiFunction<LexerToken, Double, GenericLiteralType> createType
    ) {
        this.literalClassName = literalClassName;
        this.preferredTypeName = preferredTypeName;
        this.negConstexpr = negConstexpr;
        this.createType = createType;
    }

}

class ConfigType {
    public String preferredTypeName;
    public BiFunction<UnificationContext, GenericLiteralType, VerifyResult> verifyAsArgument;

    ConfigType(String preferredTypeName, BiFunction<UnificationContext, GenericLiteralType, VerifyResult> verifyAsArgument) {
        this.preferredTypeName = preferredTypeName;
        this.verifyAsArgument = verifyAsArgument;
    }
}

class DoubleLiteral extends GenericLiteral {
    DoubleLiteral(LexerToken origin, double value) {
        super(origin, value, AllFunction.doubleConfig);
    }
}

class DoubleLiteralType extends GenericLiteralType {

    DoubleLiteralType(LexerToken origin, double value) {
        super(origin, value, AllFunction.doubleConfigType);
    }
}

class FloatLiteral extends GenericLiteral {

    FloatLiteral(LexerToken origin, double value) {
        super(origin, value, AllFunction.floatConfig);
    }
}

class FloatLiteralType extends GenericLiteralType {
    FloatLiteralType(LexerToken origin, double value) {
        super(origin, value, AllFunction.floatConfigType);
    }
}

class UintLiteral extends GenericLiteral {

    UintLiteral(LexerToken origin, long value) {
        super(origin, value, AllFunction.uintConfig);
    }

}

class VerifyResult {
    public boolean result = false;
    public String reason = "";

    VerifyResult(boolean result) {
        this.result = result;
    }

    VerifyResult(boolean result, String reason) {
        this.result = result;
        this.reason = reason;
    }
}

class ResultingFunc {
    public Func func1;
    public List<Node> typeArguments;

    ResultingFunc(Func func1, List<Node> typeArguments) {
        this.func1 = func1;
        this.typeArguments = typeArguments;
    }
}

class FindTypeVariable extends Visitor {
    private Func func1;
    private List<Node> typeArguments;

    FindTypeVariable(Func func1, List<Node> typeArguments) {
        super();
        this.func1 = func1;
        this.typeArguments = typeArguments;
    }

    public void visitTypeRef(Node node) {
        for (Node typeArgument : ((TypeRef) node).typeArguments) {
            typeArgument.visit(this);
        }
    }

    public void visitTypeVariable(Node node) {
        throw new Error("Unexpected type variable: " + node.toString() + " when instantiating " + this.func1.toString() + " with arguments " + this.typeArguments);
    }
}

class InstantiationSubstitution extends Substitution {
    public FuncInstantiator thisInstantiator;

    InstantiationSubstitution(FuncInstantiator thisInstantiator, List<Node> argumentList, List<Node> typeArguments) {
        super(argumentList, typeArguments);
        this.thisInstantiator = thisInstantiator;
    }

    public CallExpression visitCallExpression(Node node) {
        CallExpression result = super.visitCallExpression(node);

        // We may have to re-resolve the function call, if it was a call to a protocol
        // signature.
        if (result.func1 instanceof ProtocolFuncDecl) {
            ResolveOverloadResult overload = AllFunction.resolveOverloadImpl(result.possibleOverloads, result.typeArguments(), result.argumentTypes, result.returnType(), false);
            if (overload.func1 == null) {
                String failureStr = overload.failures.stream().map((OverloadResolutionFailure failure) -> {
                   return failure.toString();
                }).collect(Collectors.joining("\n"));
                throw new Error(
                        "Could not resolve protocol signature function call during instantiation: " + result.func1.toString() +
                                (overload.failures.size() != 0 ? "; tried:\\n" + failureStr : "")
                );
            }
            ((CallExpression) result).resolveToOverload(overload);
        }
        if (result.func1.isNative() == true) {
            result.nativeFuncInstance = this.thisInstantiator.getUnique((Func) result.func1, result.actualTypeArguments);
        }
        return result;
    }
}

class Instantiate extends Base {
    public InstantiationSubstitution substitution;
    public InstantiationInstantiateImmediates instantiateImmediates;

    Instantiate(InstantiationSubstitution substitution, InstantiationInstantiateImmediates instantiateImmediates) {
        super();
        this.substitution = substitution;
        this.instantiateImmediates = instantiateImmediates;
    }

    public FuncDef visitFuncDef(Node func1) {
        FuncDef newFunc = (FuncDef) func1;
        HmType returnType = (HmType) newFunc.returnType().visit(this.substitution);
        returnType = (HmType) returnType.visit(this.instantiateImmediates);
        List<FuncParameter> parameters = newFunc.parameters().stream().map((FuncParameter $0) -> (FuncParameter) $0.visit(this.substitution)).collect(Collectors.toList());
        parameters = parameters.stream().map((FuncParameter $0) -> (FuncParameter) $0.visit(this.instantiateImmediates)).collect(Collectors.toList());
        Block body = (Block) newFunc.body().visit(this.substitution);
        body = (Block) body.visit(this.instantiateImmediates);
        return new FuncDef(newFunc.origin, newFunc.name(), returnType, new ArrayList<>(), parameters, body, newFunc.isCast(), newFunc.shaderType());
    }

    public NativeFuncInstance visitNativeFunc(Node func1) {
        NativeFunc newFunc = (NativeFunc) func1;
        return new NativeFuncInstance(
                newFunc,
                (HmType) ((HmType) newFunc.returnType().visit(this.substitution)).visit(this.instantiateImmediates),
                newFunc.parameters().stream().map(
                        (FuncParameter parameter) -> (FuncParameter) ((FuncParameter) parameter.visit(this.substitution)).visit(this.instantiateImmediates)
                ).collect(Collectors.toList()),
                newFunc.isCast(),
                newFunc.shaderType(),
                newFunc.instantiateImplementation.apply(this.substitution)
        );
    }

    @Override
    public Map<String, Function<Base, Object>> mapReturn() {
        Map<String, Function<Base, Object>> methods = super.mapReturn();
        methods.put("visitFuncDef", base -> visitFuncDef((Node) base));
        methods.put("visitNativeFunc", base -> visitNativeFunc((Node) base));
        return methods;
    }

}

class StateResult {
    public int index;
    public List<LexerToken> stack;

    StateResult(int index, List<LexerToken> stack) {
        this.index = index;
        this.stack = stack;
    }
}

class KindArray {
    public String kind;
    public List<Func> array = new ArrayList<>();
}

class WhileLoop extends Node {
    private final HmValue _conditional;

    WhileLoop(LexerToken origin, HmValue conditional, Node body) {
        super();
        this._conditional = conditional;
        this._body = body;
        this.origin = origin;
    }

    public HmValue conditional() {
        return this._conditional;
    }

    public String toString() {
        return "while (" + this.conditional().toString() + ") " + this._body.toString();
    }
}

class ImplementationDataType {

    public StructType hmType;
    public String fieldName = "";
    public int offset = 0;
    public int structSize = 0;
    public int fieldSize = 0;

    ImplementationDataType(StructType hmType, String fieldName) {
        this.hmType = hmType;
        this.fieldName = fieldName;
    }
}

class ResultMemberInfo {
    public int value;
    public String name;

    ResultMemberInfo(int value, String name) {
        this.value = value;
        this.name = name;
    }
}

class CallResolveResult {
    public CallExpression call;
    public HmType resultType;


    CallResolveResult(CallExpression call, HmType resultType) {
        this.call = call;
        this.resultType = resultType;
    }
}

class InstantiationInstantiateImmediates extends InstantiateImmediates {
    public CallExpression visitCallExpression(Node node) {
        // We need to preserve certain things that would have instantiated, but that we cannot
        // instantiate without breaking chain-instantiations (generic function calls generic
        // function so therefore the instantiated generic function must still have the original
        // (uninstantiated) types to instantiate the generic function that it calls).
        // Array<VariableRef|MakePtrExpression|MakeArrayRefExpression|GenericLiteral|DereferenceExpression|DotExpression>
        CallExpression newNode = (CallExpression) node;
        CallExpression result = new CallExpression(
                newNode.origin,
                newNode.name(),
                newNode.typeArguments(),
                newNode.argumentList().stream().map((Node argument) -> {
                    return (Expression) Node.visit(argument, this);
                }).collect(Collectors.toList())
        );
        result = this.processDerivedCallData(newNode, result);

        result.argumentTypes = newNode.argumentTypes;
        if (newNode.isCast()) {
            result.setCastData(newNode.returnType());
        }
        result.actualTypeArguments = newNode.actualTypeArguments;

        return result;
    }
}

class IntLiteralType extends GenericLiteralType {
    IntLiteralType(LexerToken origin, int value) {
        super(origin, value, AllFunction.intConfigType);
    }
}

class IntLiteral extends GenericLiteral {

    IntLiteral(LexerToken origin, int value) {
        super(origin, value, AllFunction.intConfig);
    }
}

class TernaryExpression extends Expression {
    TernaryExpression(LexerToken operator, Expression predicate, Expression expression, Expression expression1) {
        super(operator);
    }
}

class RValueFinder extends Base {

    public Function<Node, Node> _visit;

    RValueFinder(Function<Node, Node> visit) {
        super();
        this._visit = visit;
    }

    public void visitDotExpression(Node node) {
        node.struct1().visit(this);
    }

    public void visitIndexExpression(Node node) {
        IndexExpression newNode = (IndexExpression) node;
        newNode.array().visit(this);
        this._visit.apply(newNode.index);
    }

    public void visitVariableRef(Node node) {
    }

    public void visitDereferenceExpression(Node node) {
        this._visit.apply(((DereferenceExpression) node).ptr());
    }

    public void visitIdentityExpression(Node node) {
        ((IdentityExpression) node).target().visit(this);
    }

    public void visitMakeArrayRefExpression(Node node) {
        this._visit.apply(((MakeArrayRefExpression) node).lValue());
    }

    @Override
    public Map<String, Function<Base, Object>> mapReturn() {
        Map<String, Function<Base, Object>> methods = super.mapReturn();

        methods.put("visitDotExpression", base -> {
            this.visitDotExpression((Node) base);
            return null;
        });
        methods.put("visitIndexExpression", base -> {
            visitIndexExpression((Node) base);
            return null;
        });
        methods.put("visitVariableRef", base -> {
            visitVariableRef((Node) base);
            return null;
        });
        methods.put("visitDereferenceExpression", base -> {
            visitDereferenceExpression((Node) base);
            return null;
        });
        methods.put("visitIdentityExpression", base -> {
            visitIdentityExpression((Node) base);
            return null;
        });
        methods.put("visitMakeArrayRefExpression", base -> {
            visitMakeArrayRefExpression((Node) base);
            return null;
        });
        return methods;
    }
}

class OverloadResolutionSuccess {
    public Func func1;
    public UnificationContext unificationContext;
    public List<Node> typeArguments;

    OverloadResolutionSuccess(Func func1, UnificationContext unificationContext, List<Node> typeArguments) {
        this.func1 = func1;
        this.unificationContext = unificationContext;
        this.typeArguments = typeArguments;
    }
}

class ResolveOverloadResult {
    public OverloadResolutionFailure failure;
    public List<OverloadResolutionFailure> failures;
    public OverloadResolutionSuccess success;
    public List<OverloadResolutionSuccess> successes;
    public Func func1;
    public UnificationContext unificationContext;
    public List<Node> typeArguments;

    ResolveOverloadResult(
            OverloadResolutionFailure failure,
            List<OverloadResolutionFailure> failures,
            OverloadResolutionSuccess success,
            List<OverloadResolutionSuccess> successes,
            Func func1,
            UnificationContext unificationContext,
            List<Node> typeArguments
    ) {
        this.failure = failure;
        this.failures = failures;
        this.success = success;
        this.successes = successes;
        this.func1 = func1;
        this.unificationContext = unificationContext;
        this.typeArguments = typeArguments;
    }
}

class ReturnStyleType {
    public String definitelyReturns = "";
    public String definitelyDoesntReturn = "";
    public String hasntReturnedYet = "";
}

class UintLiteralType extends GenericLiteralType {
    UintLiteralType(LexerToken origin, Double value) {
        super(origin, value, AllFunction.uintConfigType);
    }
}
class AllFunction implements SetupImplementationDataInterface, GenericParseLeftInterface, NullTypeVariableUnifyInterface {

    static Program standardProgram;

    public static Object kind;

    public static ResolveOverloadResult resolveOverloadImpl(List<? extends Func> functions, List<Node> typeArguments, List<? extends Node> argumentTypes, TypeRef returnType, boolean allowEntryPoint) {
        if (functions.size() == 0) {
            throw new Error("Null functions; that should have been caught by the caller.");
        }
        List<OverloadResolutionFailure> failures = new ArrayList<>();
        List<OverloadResolutionSuccess> successes = new ArrayList<>();

        for (Func func1 : functions) {
            if (!allowEntryPoint && func1.shaderType() != null) {
                failures.add(new OverloadResolutionFailure(func1, "Function is a " + func1.shaderType() + " shader, so it cannot be called from within an existing shader."));
                continue;
            }
            Object overload = inferTypesForCall(func1, typeArguments, argumentTypes, returnType);
            if (overload instanceof ResolveOverloadResult && ((ResolveOverloadResult) overload).failure != null) {
                failures.add(((ResolveOverloadResult) overload).failure);
            } else {
                successes.add((OverloadResolutionSuccess) overload);
            }
        }
        if (successes.size() == 0) {
            return new ResolveOverloadResult(null, failures, null, null, null, null, null);
        }

        int minimumConversionCost = successes.stream().mapToInt((success) -> success.unificationContext.conversionCost()).reduce((result, overload) -> Math.min(result, overload)).getAsInt();
        successes = successes.stream().filter((overload) -> overload.unificationContext.conversionCost() == minimumConversionCost).collect(Collectors.toList());

        // If any of the signatures are restricted then we consider those first. This is an escape mechanism for
        // built-in things.
        // FIXME: It should be an error to declare a function that is at least as specific as a restricted function.
        // https://bugs.webkit.org/show_bug.cgi?id=176580
        boolean hasRestricted = successes.stream().map((success) -> success.func1.isRestricted).reduce((result, overload) -> result || overload).get();
        if (hasRestricted) {
            successes = successes.stream().filter((overload) -> overload.func1.isRestricted).collect(Collectors.toList());
        }
        // We are only interested in functions that are at least as specific as all of the others. This means
        // that they can be "turned around" and applied onto all of the other functions in the list.
        List<OverloadResolutionSuccess> prunedSuccesses = new ArrayList<>();
        for (int i = 0; i < successes.size(); ++i) {
            boolean ok = true;
            Func argumentFunc = successes.get(i).func1;
            for (int j = 0; j < successes.size(); ++j) {
                if (i == j) {
                    continue;
                }
                Func parameterFunc = successes.get(j).func1;
                Object overload = inferTypesForCall(
                        parameterFunc,
                        typeArguments.size() > 0 ? argumentFunc.typeParameters() : new ArrayList<>(),
                        argumentFunc.parameterTypes(),
                        (TypeRef) argumentFunc.returnTypeForOverloadResolution());
                if (overload instanceof ResolveOverloadResult) {
                    if (((ResolveOverloadResult) overload).func1 == null) {
                        ok = false;
                        break;
                    }
                } else if (overload instanceof OverloadResolutionSuccess) {
                    if (((OverloadResolutionSuccess) overload).func1 == null) {
                        ok = false;
                        break;
                    }
                }
            }
            if (ok) {
                prunedSuccesses.add(successes.get(i));
            }
        }
        if (prunedSuccesses.size() == 1) {
            return new ResolveOverloadResult(null, null, prunedSuccesses.get(0), null, prunedSuccesses.get(0).func1, prunedSuccesses.get(0).unificationContext, prunedSuccesses.get(0).typeArguments);
        }
        List<OverloadResolutionSuccess> ambiguityList;
        String message;
        if (prunedSuccesses.size() == 0) {
            ambiguityList = successes;
            message = "Ambiguous overload - no function can be applied to all others";
        } else {
            ambiguityList = prunedSuccesses;
            message = "Ambiguous overload - functions mutually applicable";
        }
        ;
        return new ResolveOverloadResult(null, ambiguityList.stream().map((overload) -> {
            return new OverloadResolutionFailure(overload.func1, message);
        }).collect(Collectors.toList()), null, null, null, null, null);
    }
// If a high-level construct somehow manages to live on when we're lowered, it's a high zombie.
    public static Object inferTypesForCall(
            Func func1,
            List<Node> typeArguments,
            List<? extends Node> argumentTypes,
            TypeRef returnType)
    {
        if (typeArguments.size() > 0 && typeArguments.size() != func1.typeParameters().size()) {
            return new ResolveOverloadResult(
                    new OverloadResolutionFailure(func1, "Wrong number of type arguments (passed " + typeArguments.size() + ", require " + func1.typeParameters().size()),
                    null,
                    null,
                    null,
                    null,
                    null,
                    null
            );
        }
        if (argumentTypes.size() != func1.parameters().size()) {
            return new ResolveOverloadResult(
                    new OverloadResolutionFailure(func1, "Wrong number of arguments (passed " + argumentTypes.size() + ", require " + func1.parameters().size()),
                    null,
                    null,
                    null,
                    null,
                    null,
                    null
            );
        }
        UnificationContext unificationContext = new UnificationContext(func1.typeParametersForCallResolution());
        for (int i = 0; i < typeArguments.size(); ++i) {
            Node argument = typeArguments.get(i);
            Node parameter = func1.typeParameters().get(i);
            if (!argument.unify(unificationContext, parameter)) {
                return new ResolveOverloadResult(
                        new OverloadResolutionFailure(
                                func1,
                                "Type argument #" + (i + 1) + " for parameter " + parameter.name() + " does not match (passed " + argument + ", require " + parameter + ")"),
                        null,
                        null,
                        null,
                        null,
                        null,
                        null
                );
            }
        }
        for (int i = 0; i < argumentTypes.size(); ++i) {
            if (argumentTypes.get(i) == null) {
                throw new Error("Null argument type at i = " + i);
            }
            if (!argumentTypes.get(i).unify(unificationContext, func1.parameters().get(i).hmType())) {
                return new ResolveOverloadResult(
                        new OverloadResolutionFailure(
                                func1,
                                "Argument #" + (i + 1) +
                                        (!func1.parameters().get(i).name().isEmpty() ? "for parameter " + func1.parameters().get(i).name() : " ") +
                                        "does not match (passed " + argumentTypes.get(i).toString() + ", require " + func1.parameters().get(i).hmType() + ")"
                        ),
                        null,
                        null,
                        null,
                        null,
                        null,
                        null
                );
            }
        }
        if (returnType != null && !returnType.unify(unificationContext, func1.returnType())) {
            return new ResolveOverloadResult(
                    new OverloadResolutionFailure(func1, "Return type " + func1.returnType() + "does not match" + returnType),
                    null,
                    null,
                    null,
                    null,
                    null,
                    null
            );
        }
        VerifyResult verificationResult = unificationContext.verify();
        if (!verificationResult.result) {
            return new ResolveOverloadResult(new OverloadResolutionFailure(func1, verificationResult.reason), null, null, null, null, null, null);
        }
        boolean shouldBuildTypeArguments = typeArguments.size() == 0;
        List<Node> newTypeArguments = typeArguments;
        if (shouldBuildTypeArguments) {
            newTypeArguments = new ArrayList<>();
        }
        for (Node typeParameter : func1.typeParameters()) {
            Node typeArgument = unificationContext.find(typeParameter);
            if (typeArgument == typeParameter) {
                return new ResolveOverloadResult(
                        new OverloadResolutionFailure(func1, "Type parameter " + typeParameter.name() + "did not get assigned a type"),
                        null,
                        null,
                        null,
                        null,
                        null,
                        null
                );
            }
            if (shouldBuildTypeArguments) {
                newTypeArguments.add(typeArgument);
            }
        }
        return new OverloadResolutionSuccess(func1, unificationContext, newTypeArguments);
    }

    public static void inline(Program program) {
        for (List<Func> funcList : program.functions().values()) {
            for (Func func1 : funcList) {
                if (func1.typeParameters().isEmpty()) {
                    Func func2 = program.funcInstantiator().getUnique(func1, new ArrayList<>());
                    inlineFunction(program, func2, new VisitingSet(Arrays.asList(func2)));
                }
            }
        }
    }


    public static void inlineFunction(Program program, Func func1, VisitingSet visiting) {
        if (func1.typeParameters().size() > 0) {
            throw new Error("Cannot inline func1tion that has type parameters");
        }
        if (func1.inlined || func1.isNative()) {
            return;
        }
        FuncDef funcDef = (FuncDef) func1;
        funcDef.visit(new LateChecker());

        // This is the precise time when we can build EBuffers in order to get them to be uniqued by
        // type instantiation but nothing else.
        funcDef.visit(new StructLayoutBuilder());
        funcDef.visit(new EBufferBuilder(program));
        funcDef.rewrite(new Inliner(program, funcDef, visiting));
        funcDef.inlined = true;
    }

    public static Object resolveInlinedFunction(
            Program program,
            String name,
            List<Node> typeArguments,
            List<HmType> argumentTypes,
            boolean allowEntryPoint
    ) {
        ResolveOverloadResult overload = program.globalNameContext().resolveFuncOverload(name, typeArguments, argumentTypes, null, allowEntryPoint);
        if (overload.func1 == null) {
            return overload.failures;
        }
        if (overload.func1.typeParameters() == null) {
            return overload.func1;
        }
        FuncDef func1 = (FuncDef) program.funcInstantiator().getUnique(overload.func1, overload.typeArguments);
        inlineFunction(program, func1, new VisitingSet(new ArrayList<>(List.of((FuncDef) overload.func1))));
        return func1;
    }

    public static LexerToken genericConsume(Function<LexerToken, Boolean> callback, List<String> explanation) {
        LexerToken token = Constants.lexer.next();
        if (token == null) {
            Constants.lexer.fail("Unexpected end of file");
        }
        //System.out.println("token._kind = " + token._kind + " token.index = " + token._index + " token._text = " + token._text );
        if (!callback.apply(token)) {
            Constants.lexer.fail("Unexpected token: " + token.text() + "; expected: " + String.join(",", explanation));
        }
        return token;
    }

    public static LexerToken consume(List<String> texts) {
        return genericConsume(token -> {
            return texts.contains(token.text());
        }, texts);
    }

    public static LexerToken consumeKind(String kind) {

        return genericConsume(
                token -> {
                    return Objects.equals(token.kind(), kind);
                },
                Arrays.asList(kind));
    }

    public static void assertNext(List<String> texts) {
        Constants.lexer.push(consume(texts));
    }

    public static LexerToken genericTest(Function<LexerToken, Boolean> callback) {
        LexerToken token = Constants.lexer.peek();
        if (token != null && callback.apply(token)) {
            return token;
        }
        return null;
    }

    public static LexerToken test(List<String> texts) {
        return genericTest(token -> {
            return texts.contains(token.text());
        });
    }

    public static LexerToken testKind(String kind) {
        return genericTest(token -> {
            return token.kind().equals(kind);
        });
    }

    public static LexerToken tryConsume(List<String> texts) {
        LexerToken result = test(texts);
        if (result != null) {
            Constants.lexer.next();
        }
        return result;
    }

    public static LexerToken tryConsumeKind(String kind) {
        LexerToken result = testKind(kind);
        if (result != null) {
            Constants.lexer.next();
        }
        return result;
    }

    public static ProtocolRef parseProtocolRef() {
        LexerToken protocolToken = consumeKind("identifier");
        return new ProtocolRef(protocolToken, protocolToken.text());
    }

    public static void consumeEndOfTypeArgs() {
        LexerToken rightShift = tryConsume(new ArrayList<>(List.of(">>")));
        if (rightShift != null) {
            Constants.lexer.push(new LexerToken(Constants.lexer, rightShift.index(), rightShift.kind(), ">"));
        } else {
            consume(List.of(">"));
        }
    }

    public static List<Node> parseTypeParameters() {
        if (test(List.of("<")) == null) {
            return new ArrayList<>();
        }

        List<Node> result = new ArrayList<>();
        consume(List.of("<"));
        while (test(List.of(">")) == null) {
            Node constexpr = Constants.lexer.backtrackingScope(() -> {
                HmType type = parseType();
                LexerToken name = consumeKind("identifier");
                assertNext(List.of(",", ">", ">>"));
                return new ConstexprTypeParameter(type.origin, name.text(), (TypeRef) type);
            });
            if (constexpr != null) {
                result.add(constexpr);
            } else {
                LexerToken name = consumeKind("identifier");
                ProtocolRef hmProtocol = tryConsume(List.of(":")) != null ? parseProtocolRef() : null;
                result.add(new TypeVariable(name, name.text(), hmProtocol));
            }
            if (tryConsume(List.of(",")) == null) {
                break;
            }
        }
        consumeEndOfTypeArgs();
        return result;
    }

    public static Expression parseTerm() {
        LexerToken token;
        token = tryConsume(List.of("null"));
        if (token != null) {
            return new NullLiteral(token);
        }
        token = tryConsumeKind("identifier");
        if (token != null) {
            return new VariableRef(token, token.text());
        }
        token = tryConsumeKind("intLiteral");
        if (token != null) {
            int intVersion = 0;
            try {
                intVersion = parseInt(token.text());
            } catch (Throwable e) {
                Constants.lexer.fail("Expected assignment");
            }
            if (!("" + intVersion).equals(token.text())) {
                Constants.lexer.fail("Integer literal is not an integer: " + token.text());
            }
            if (intVersion > Constants.WSL_INT32MAX) {
                Constants.lexer.fail("Expected assignment");
            }
            return new IntLiteral(token, intVersion);
        }
        token = tryConsumeKind("uintLiteral");
        if (token != null) {
            int uintVersion = parseInt(token.text().substring(0, token.text().length() - 1));
            if (!(uintVersion + "u").equals(token.text())) {
                Constants.lexer.fail("Integer literal is not 32-bit unsigned integer: " + token.text());
            }
            return new UintLiteral(token, uintVersion);
        }
        token = tryConsumeKind("intHexLiteral");
        if (token == null) {
            token = tryConsumeKind("uintHexLiteral");
        }
        if (token != null) {
            String hexString = token.text().substring(Constants.WSL_2);
            if (token.kind().equals("uintHexLiteral")) {
                hexString = hexString.substring(0, hexString.length() - 1);
            }
            if (hexString.length() == 0) {
                throw new Error("Bad hex literal: " + token);
            }
            int intVersion = parseInt(hexString, Constants.WSL_16);
            if (token.kind().equals("intHexLiteral")) {
                intVersion = intVersion | 0;
            } else {
                intVersion = intVersion >>> 0;
            }
            if (!Integer.toString(intVersion, Constants.WSL_16).equals(hexString)) {
                Constants.lexer.fail("Hex integer literal is not an integer: " + token.text());
            }
            if (token.kind().equals("'intHexLiteral'")) {
                return new IntLiteral(token, intVersion);
            }
            return new UintLiteral(token, intVersion >>> 0);
        }
        token = tryConsumeKind("doubleLiteral");
        if (token != null) {
            return new DoubleLiteral(token, Double.parseDouble(token.text()));
        }
        token = tryConsumeKind("floatLiteral");
        if (token != null) {
            String text = token.text();
            boolean d = text.endsWith("d");
            boolean f = text.endsWith("f");
            if (d && f) {
                throw new Error("Literal cannot be both a double literal and a float literal.");
            }
            if (d || f) {
                text = text.substring(0, text.length() - 1);
            }
            double value = Double.parseDouble(text);
            if (d) {
                return new DoubleLiteral(token, value);
            }
            return new FloatLiteral(token, value);
        }
        token = tryConsume(List.of("true", "false"));
        if (token != null) {
            return new BoolLiteral(token, token.text().equals("true"));
        }
        // FIXME: Need support for other literals too.
        consume(List.of("("));
        Expression result = parseExpression();
        consume(List.of(")"));
        return result;
    }

    public static Expression parseConstexpr() {
        LexerToken token;
        token = tryConsume(List.of("-"));
        if (token != null) {
            return new CallExpression(token, "operator" + token.text(), new ArrayList<>(), new ArrayList<>(List.of(parseTerm())));
        }
        Expression left = parseTerm();
        token = tryConsume(List.of("."));
        if (token != null) {
            left = new DotExpression(token, left, consumeKind("identifier").text());
        }
        return left;
    }

    public static List<Node> parseTypeArguments() {
        if (test(List.of("<")) == null) {
            return new ArrayList<>();
        }

        List<Node> result = new ArrayList<>();
        consume(List.of("<"));
        while (test(List.of(">")) == null) {
            // It's possible for a constexpr or type can syntactically overlap in the single
            // identifier case. Let's consider the possibilities:
            //
            //     T          could be type or constexpr
            //     T[]        only type
            //     T[42]      only type (constexpr cannot do indexing)
            //     42         only constexpr
            //
            // In the future we'll allow constexprs to do more things, and then we'll still have
            // the problem that something of the form T[1][2][3]... can either be a type or a
            // constexpr, and we can figure out in the checker which it is.
            Node typeOrVariableRef = Constants.lexer.backtrackingScope(() -> {
                LexerToken result2 = consumeKind("identifier");
                assertNext(List.of(",", ">", ">>"));
                return new TypeOrVariableRef(result2, result2.text());
            });
            if (typeOrVariableRef != null) {
                result.add(typeOrVariableRef);
            } else {
                Node constexpr = Constants.lexer.backtrackingScope(() -> {
                    Expression result2 = parseConstexpr();
                    assertNext(List.of(",", ">", ">>"));
                    return result2;
                });
                if (constexpr != null) {
                    result.add(constexpr);
                } else {
                    result.add(parseType());
                }
            }
            if (tryConsume(List.of(",")) == null) {
                break;
            }
        }
        consumeEndOfTypeArgs();
        return result;
    }

    public static HmType parseType() {
        LexerToken token;
        String addressSpace = null;
        final boolean[] addressSpaceConsumed = {false};
        token = tryConsume(Constants.addressSpaces);
        if (token != null) {
            addressSpace = token.text();
        }

        LexerToken name = consumeKind("identifier");
        List<Node> typeArguments = parseTypeArguments();
        HmType hmType = new TypeRef(name, name.text(), typeArguments);

        String finalAddressSpace = addressSpace;
        Supplier<String> getAddressSpace = new Supplier<String>() {
            @Override
            public String get() {
                addressSpaceConsumed[0] = true;
                if (finalAddressSpace != null) {
                    return finalAddressSpace;
                }
                return consume(Constants.addressSpaces).text();
            }
        };

        while (true) {
            token = tryConsume(List.of("*", "["));
            if (token == null) {
                break;
            }
            if (token.text().equals("*")) {
                hmType = new PtrType(token, getAddressSpace.get(), hmType);
                continue;
            }
            if (tryConsume(List.of("]")) != null) {
                hmType = new ArrayRefType(token, getAddressSpace.get(), hmType);
                continue;
            }

            hmType = new ArrayType(token, hmType, parseConstexpr());
            consume(List.of("]"));
        }

        if (addressSpace != null && !addressSpaceConsumed[0]) {
            Constants.lexer.fail("Address space specified for type that does not need address space");
        }
        return hmType;

    }

    public static Node parseTypeDef() {
        LexerToken origin = consume(List.of("typedef"));
        String name = consumeKind("identifier").text();
        List<Node> typeParameters = parseTypeParameters();
        consume(List.of("="));
        HmType type = parseType();
        consume(List.of(";"));
        return new TypeDef(origin, name, typeParameters, type);
    }

    public static Expression genericParseLeft(
            List<String> texts,
            Supplier<Expression> nextParser,
            GenericParseLeftInterface constructor
    ) {
        Expression left = nextParser.get();
        LexerToken token = null;
        while (true) {
            token = tryConsume(texts);
            if (token == null) {
                break;
            }
            left = constructor.apply(token, left, nextParser.get());
        }
        return left;
    }

    public static Expression parseLeftOperatorCall(List<String> texts, Supplier<Expression> nextParser) {
        return genericParseLeft(
                texts,
                nextParser,
                (token, hmLeft, hmRight) -> new CallExpression(token, "operator" + token.text(), new ArrayList<>(), new ArrayList<HmValue>(List.of(hmLeft, hmRight)))
        );
    }

    public static CallExpression parseCallExpression() {
        LexerToken name = consumeKind("identifier");
        List<Node> typeArguments = parseTypeArguments();
        consume(List.of("("));
        List<Expression> argumentList = new ArrayList<>();
        while (test(List.of(")")) == null) {
            Expression argument = parsePossibleAssignment(null);
            argumentList.add(argument);
            if (tryConsume(List.of(",")) == null) {
                break;
            }
        }
        consume(List.of(")"));
        CallExpression result = new CallExpression(name, name.text(), typeArguments, argumentList);
        return  result;
    }

    public static boolean isCallExpression() {
        return Constants.lexer.testScope(() -> {
            consumeKind("identifier");
            parseTypeArguments();
            consume(List.of("("));
        });
    }

    public static CallExpression emitIncrement(LexerToken token, Expression old, Expression extraArg) {
        List<Expression> args = new ArrayList<>(List.of(old));
        if (extraArg != null) {
            args.add(extraArg);
        }
        String name = "operator" + token.text();
        Matcher matcher =  Pattern.compile("=$").matcher(name);
        if (matcher.find()) {
            name = name.substring(0, matcher.start());
        }
        if (Objects.equals(name,"operator")) {
            throw new Error("Invalid name: " + name);
        }
        return new CallExpression(token, name, new ArrayList<>(), args);
    }

    public static Expression finishParsingPostIncrement(LexerToken token, Expression hmLeft) {
        ReadModifyWriteExpression readModifyWrite = new ReadModifyWriteExpression(token, hmLeft, null);
        readModifyWrite.newValueExp = emitIncrement(token, readModifyWrite.oldValueRef(), null);
        readModifyWrite.resultExp = readModifyWrite.oldValueRef();
        return readModifyWrite;
    }

    public static Expression parseSuffixOperator(Expression hmLeftValue, List<String> acceptableOperators) {
        Expression hmLeft = hmLeftValue;
        LexerToken token = null;
        while (true) {
            token = tryConsume(acceptableOperators);
            if (token == null) {
                break;
            }
            switch (token.text()) {
                case "++":
                case "--":
                    return finishParsingPostIncrement(token, hmLeft);
                case ".":
                case "->":
                    if (token.text().equals("->")) {
                        hmLeft = new DereferenceExpression(token, hmLeft, null, null);
                    }
                    hmLeft = new DotExpression(token, hmLeft, consumeKind("identifier").text());
                    break;
                case "[":
                    Expression index = AllFunction.parseExpression();
                    consume(List.of("]"));
                    hmLeft = new IndexExpression(token, hmLeft, index);
                    break;
                default:
                    throw new Error("Bad token: " + token);
            }
        }
        return hmLeft;
    }

    public static Expression parsePossibleSuffix() {
        List<String> acceptableOperators = List.of("++", "--", ".", "->", "[");
        List<String> limitedOperators = List.of(".", "->", "[");
        ;
        Expression left;
        if (isCallExpression()) {
            left = parseCallExpression();
            acceptableOperators = limitedOperators;
        } else {
            left = parseTerm();
        }
        return parseSuffixOperator(left, acceptableOperators);
    }

    public static ReadModifyWriteExpression finishParsingPreIncrement(LexerToken token, Expression hmLeft, Expression extraArg) {
        ReadModifyWriteExpression readModifyWrite = new ReadModifyWriteExpression(token, hmLeft, null);
        readModifyWrite.newValueExp = emitIncrement(token, readModifyWrite.oldValueRef(), extraArg);
        readModifyWrite.resultExp = readModifyWrite.newValueRef();
        return readModifyWrite;
    }

    public static ReadModifyWriteExpression parsePreIncrement() {
        LexerToken token = consume(List.of("++", "--"));
        Expression left = parsePossiblePrefix();
        return finishParsingPreIncrement(token, left, null);
    }

    public static Expression parsePossiblePrefix() {
        LexerToken token;
        if (test(List.of("++", "--")) != null) {
            return parsePreIncrement();
        }
        token = tryConsume(List.of("+", "-", "~"));
        if (token != null) {
            return new CallExpression(token, "operator" + token.text(), new ArrayList<>(), new ArrayList<>(List.of(parsePossiblePrefix())));
        }
        token = tryConsume(List.of("*"));
        if (token != null) {
            return new DereferenceExpression(token, parsePossiblePrefix(), null, null);
        }
        token = tryConsume(List.of("&"));
        if (token != null) {
            return new MakePtrExpression(token, parsePossiblePrefix());
        }
        token = tryConsume(List.of("@"));
        if (token != null) {
            return new MakeArrayRefExpression(token, parsePossiblePrefix());
        }
        token = tryConsume(List.of("!"));
        if (token != null) {
            Expression remainder = parsePossiblePrefix();
            return new LogicalNot(token, new CallExpression(remainder.origin, "bool", new ArrayList<>(), new ArrayList<>(List.of(remainder))));
        }
        return parsePossibleSuffix();
    }

    public static Expression parsePossibleProduct() {
        return parseLeftOperatorCall(List.of("*", "/", "%"), AllFunction::parsePossiblePrefix);
    }

    public static Expression parsePossibleSum() {
        return parseLeftOperatorCall(List.of("+", "-"), AllFunction::parsePossibleProduct);
    }

    public static Expression parsePossibleShift() {
        return parseLeftOperatorCall(List.of("<<", ">>"), AllFunction::parsePossibleSum);
    }

    public static Expression parsePossibleRelationalInequality() {
        return parseLeftOperatorCall(List.of("<", ">", "<=", ">="), AllFunction::parsePossibleShift);
    }

    public static Expression parsePossibleRelationalEquality() {
        return genericParseLeft(List.of("==", "!="), AllFunction::parsePossibleRelationalInequality, (token, hmLeft, hmRight) -> {
            Expression result = new CallExpression(token, "operator==", new ArrayList<>(), new ArrayList<>(List.of(hmLeft, hmRight)));
            if (token.text().equals("!=")) {
                result = new LogicalNot(token, result);
            }
            return result;
        });
    }

    public static Expression parsePossibleBitwiseAnd() {
        return parseLeftOperatorCall(List.of("&"), AllFunction::parsePossibleRelationalEquality);
    }

    public static Expression parsePossibleBitwiseXor() {
        return parseLeftOperatorCall(List.of("^"), AllFunction::parsePossibleBitwiseAnd);
    }

    public static Expression parsePossibleBitwiseOr() {
        return parseLeftOperatorCall(List.of("|"), AllFunction::parsePossibleBitwiseXor);
    }

    public static Expression parseLeftLogicalExpression(List<String> texts, Supplier<Expression> nextParser) {

        return genericParseLeft(texts, nextParser, (LexerToken token, Expression hmLeft, Expression hmRight) -> {
            return new LogicalExpression(
                    token,
                    token.text(),
                    new CallExpression(hmLeft.origin, "bool", new ArrayList<>(), List.of(hmLeft)),
                    new CallExpression(hmRight.origin, "bool", new ArrayList<>(), List.of(hmRight)));
        });
    }

    public static Expression parsePossibleLogicalAnd() {
        return parseLeftLogicalExpression(List.of("&&"), AllFunction::parsePossibleBitwiseOr);
    }

    public static Expression parsePossibleLogicalOr() {
        return parseLeftLogicalExpression(List.of("||"), AllFunction::parsePossibleLogicalAnd);
    }

    public static Expression parsePossibleTernaryConditional() {
        Expression predicate = parsePossibleLogicalOr();
        LexerToken _operator = tryConsume(List.of("?"));
        if (_operator == null) {
            return predicate;
        }

        return new TernaryExpression(_operator, predicate, parsePossibleAssignment(null), parsePossibleAssignment(null));
    }

    public static Expression parsePossibleAssignment(String mode) {
        Expression lhs = parsePossibleTernaryConditional();
        LexerToken operator = tryConsume(List.of("=", "+=", "-=", "*=", "/=", "%=", "^=", "|=", "&="));
        if (operator == null) {
            if (Objects.equals(mode,"required")) {
                Constants.lexer.fail("Expected assignment");
            }
            return lhs;
        }
        if (operator.text().equals("=")) {
            return new Assignment(operator, lhs, parsePossibleAssignment(null), null);
        }
        return finishParsingPreIncrement(operator, lhs, parsePossibleAssignment(null));
    }

    public static Expression parseAssignment() {
        return parsePossibleAssignment("required");
    }

    public static Expression parsePostIncrement() {
        Expression left = parseSuffixOperator(parseTerm(), List.of(".", "->", "["));
        LexerToken token = consume(List.of("++", "--"));
        return finishParsingPostIncrement(token, left);
    }

    public static Expression parseEffectfulExpression(String mode) {
        if (isCallExpression()) {
            return parseCallExpression();
        }
        Node preIncrement = Constants.lexer.backtrackingScope(AllFunction::parsePreIncrement);
        if (preIncrement != null) {
            return (Expression) preIncrement;
        }
        Node postIncrement = Constants.lexer.backtrackingScope(AllFunction::parsePostIncrement);
        if (postIncrement != null) {
            return (Expression) postIncrement;
        }
        return parseAssignment();
    }

    public static Expression genericParseCommaExpression(Function<String, Expression> finalExpressionParser) {
        List<Expression> list = new ArrayList<>();
        LexerToken origin = Constants.lexer.peek();
        if (origin == null) {
            Constants.lexer.fail("Unexpected end of file");
        }
        while (true) {
            Node effectfulExpression = Constants.lexer.backtrackingScope(() -> {
                AllFunction.parseEffectfulExpression(null);
                consume(List.of(","));
                return null;
            });
            if (effectfulExpression == null) {
                Expression final1 = finalExpressionParser.apply(null);
                list.add(final1);
                break;
            }
            list.add((Expression) effectfulExpression);
        }
        if (list.size() == 0) {
            throw new Error("Length should never be zero");
        }
        if (list.size() == 1) {
            return list.get(0);
        }
        return new CommaExpression(origin, list.stream().map((tmp) -> (HmValue) tmp).collect(Collectors.toList()));
    }

    public static Expression parseCommaExpression() {
        return genericParseCommaExpression(AllFunction::parsePossibleAssignment);
    }

    public static Expression parseExpression() {
        return parseCommaExpression();
    }

    public static Expression parseEffectfulStatement() {
        Expression result = genericParseCommaExpression(AllFunction::parseEffectfulExpression);
        consume(List.of(";"));
        return result;
    }

    public static Return parseReturn() {
        LexerToken origin = consume(List.of("return"));
        if (tryConsume(List.of(";")) != null) {
            return new Return(origin, null);
        }
        Expression expression = parseExpression();
        consume(List.of(";"));
        return new Return(origin, expression);
    }

    public static Break parseBreak() {
        LexerToken origin = consume(List.of("break"));
        consume(List.of(";"));
        return new Break(origin);
    }

    public static Continue parseContinue() {
        LexerToken origin = consume(List.of("continue"));
        consume(List.of(";"));
        return new Continue(origin);
    }

    public static IfStatement parseIfStatement() {
        LexerToken origin = consume(List.of("if"));
        consume(List.of("("));
        Expression conditional = parseExpression();
        consume(List.of(")"));
        Node body = parseStatement();
        Node elseBody = null;
        if (tryConsume(List.of("else")) != null) {
            elseBody = parseStatement();
        }
        return new IfStatement(origin, new CallExpression(conditional.origin, "bool", new ArrayList<>(), new ArrayList<>(List.of(conditional))), body, elseBody);
    }

    public static WhileLoop parseWhile() {
        LexerToken origin = consume(List.of("while"));
        consume(List.of("("));
        Expression conditional = parseExpression();
        consume(List.of(")"));
        Node body = parseStatement();
        return new WhileLoop(origin, new CallExpression(conditional.origin, "bool", new ArrayList<>(), new ArrayList<>(List.of(conditional))), body);
    }

    public static ForLoop parseFor() {
        LexerToken origin = consume(List.of("for"));
        consume(List.of("("));
        Node initialization;
        if (tryConsume(List.of(";")) != null) {
            initialization = null;
        } else {
            initialization = Constants.lexer.backtrackingScope(AllFunction::parseVariableDecls);
            if (initialization == null) {
                initialization = parseEffectfulStatement();
            }
        }
        Expression condition;
        LexerToken con1 = tryConsume(List.of(";"));
        if (con1 != null) {
            condition = null;
        } else {
            condition = parseExpression();
            consume(List.of(";"));
            condition = new CallExpression(condition.origin, "bool", new ArrayList<>(), new ArrayList<>(List.of(condition)));
        }
        Expression increment;
        if (tryConsume(List.of(")")) != null) {
            increment = null;
        } else {
            increment = parseExpression();
            consume(List.of(")"));
        }
        Node body = parseStatement();
        return new ForLoop(origin, (Expression) initialization, condition, increment, body);
    }

    public static DoWhileLoop parseDo() {
        LexerToken origin = consume(List.of("do"));
        Node body = parseStatement();
        consume(List.of("while"));
        consume(List.of("("));
        Expression conditional = parseExpression();
        consume(List.of(")"));
        return new DoWhileLoop(origin, (Block) body, new CallExpression(conditional.origin, "bool", new ArrayList<>(), new ArrayList<>(List.of(conditional))));
    }

    public static CommaExpression parseVariableDecls() {
        HmType type = parseType();
        List<HmValue> list = new ArrayList<>();
        do {
            LexerToken name = consumeKind("identifier");
            Expression initializer = tryConsume(List.of("=")) != null ? parseExpression() : null;
            list.add(new VariableDecl(name, name.text(), type, initializer));
        } while (consume(List.of(",", ";")).text().equals(","));
        return new CommaExpression(type.origin, list);
    }

    ;

    public static SwitchCase parseSwitchCase() {
        LexerToken token = consume(List.of("default", "case"));
        Expression value = null;
        if (token.text().equals("case")) {
            value = parseConstexpr();
        }
        consume(List.of(":"));
        Block body = parseBlockBody(List.of("}", "default", "case"));
        return new SwitchCase(token, value, body);
    }

    public static SwitchStatement parseSwitchStatement() {
        LexerToken origin = consume(List.of("switch"));
        consume(List.of("("));
        Expression value = parseExpression();
        consume(List.of(")"));
        consume(List.of("{"));
        SwitchStatement result = new SwitchStatement(origin, value);
        while (tryConsume(List.of("}")) == null) {
            result.add(parseSwitchCase());
        }
        return result;
    }

    public static Node parseStatement() {
        LexerToken token = Constants.lexer.peek();
        if (token != null && token.text().equals(";")) {
            Constants.lexer.next();
            return null;
        }
        if (token != null && token.text().equals("return")) {
            return parseReturn();
        }
        if (token != null && token.text().equals("break")) {
            return parseBreak();
        }
        if (token != null && token.text().equals("continue")) {
            return parseContinue();
        }
        if (token != null && token.text().equals("while")) {
            return parseWhile();
        }
        if (token != null && token.text().equals("do")) {
            return parseDo();
        }
        if (token != null && token.text().equals("for")) {
            return parseFor();
        }
        if (token != null && token.text().equals("if")) {
            return parseIfStatement();
        }
        if (token != null && token.text().equals("switch")) {
            return parseSwitchStatement();
        }
        if (token != null && token.text().equals("trap")) {
            LexerToken origin = consume(List.of("trap"));
            consume(List.of(";"));
            return new TrapStatement(origin);
        }
        if (token != null && token.text().equals("{")) {
            return parseBlock();
        }
        Node variableDecl = Constants.lexer.backtrackingScope(AllFunction::parseVariableDecls);
        if (variableDecl != null) {
            return variableDecl;
        }
        return parseEffectfulStatement();
    }

    public static Block parseBlockBody(List<String> terminators) {
        Block block = new Block(Constants.hmOrigin);
        while (test(terminators) == null) {
            Node statement = parseStatement();
            if (statement != null) {
                block.add(statement);
            }
        }
        return block;
    }

    public static boolean setLastMatch(Pattern regex, String text) {
        Matcher result = regex.matcher(text);
        boolean isMatcher = false;
        if (result.find()) {
            isMatcher = true;
            Constants.regExpLastMatch = result.group();
        }
        return isMatcher;
    }

    public static Program prepare(String origin, int lineNumberOffset, String text) {
        //Program standardProgram = null;
        if (standardProgram == null) {
            standardProgram = new Program();
            int firstLineOfStandardLibrary = 28; // See StandardLibrary.js.
            forMaxDepth();
            AllFunction.parse(standardProgram, "/internal/stdlib", "native", firstLineOfStandardLibrary - 1, standardLibrary);
        }
        Program program = cloneProgram(standardProgram);
        if (true) {
            parse(program, origin, "user", lineNumberOffset, text);
            program = programWithUnnecessaryThingsRemoved(program);
        }
        foldConstexprs(program);
        NameResolver nameResolver = createNameResolver(program);
        resolveNamesInTypes(program, nameResolver);
        resolveNamesInProtocols(program, nameResolver);
        resolveTypeDefsInTypes(program);
        resolveTypeDefsInProtocols(program);
        checkRecursiveTypes(program);
        synthesizeStructAccessors(program);
        synthesizeEnumFunctions(program);
        resolveNamesInFunctions(program, nameResolver);
        resolveTypeDefsInFunctions(program);
        flattenProtocolExtends(program);
        check(program);
        checkLiteralTypes(program);
        resolveProperties(program);
        findHighZombies(program);
        checkLiteralTypes(program);
        checkProgramWrapped(program);
        checkReturns(program);
        checkUnreachableCode(program);
        checkLoops(program);
        checkRecursion(program);
        checkProgramWrapped(program);
        findHighZombies(program);
        inline(program);
        return program;
    }

    public static void parse(Program program, String origin, String originKind, int lineNumberOffset, String text) {
        Constants.lexer = new Lexer(origin, originKind, lineNumberOffset, text);
        Constants.hmOrigin = origin;

        // The hardest part of dealing with C-like languages is parsing variable declaration statements.
        // Let's consider if this happens in WSL. Here are the valid statements in WSL that being with an
        // identifier, if we assume that any expression can be a standalone statement.
        //
        //     x;
        //     x <binop> y;
        //     x < y;
        //     x < y > z;
        //     x = y;
        //     x.f = y;
        //     \exp = y;
        //     x[42] = y;
        //     x();
        //     x<y>();
        //     x y;
        //     x<y> z;
        //     device x[] y;
        //     x[42] y;
        //     device x^ y;
        //     thread x^^ y;
        //     x^thread^thread y;
        //     x^device^thread y;
        //
        // This has two problem areas:
        //
        //     - x<y>z can parse two different ways (as (x < y) > z or x<y> z).
        //     - x[42] could become either an assignment or a variable declaration.
        //     - x<y> could become either an assignment or a variable declaration.
        //
        // We solve the first problem by forbidding expressions as statements. The lack of function
        // pointers means that we can still allow function call statements - unlike in C, those cannot
        // have arbitrary expressions as the callee. The remaining two problems are solved by
        // backtracking. In all other respects, this is a simple recursive descent parser.

        while (true) {
            LexerToken token = Constants.lexer.peek();
            if (token == null) {
                return;
            }
            if (token.text().equals(";")) {
                Constants.lexer.next();
            } else if (token.text().equals("typedef")) {
                program.add(parseTypeDef());
            } else if (originKind.equals("native") && token.text().equals("native")) {
                program.add(parseNative());
            } else if (originKind.equals("native") && token.text().equals("restricted")) {
                program.add(parseRestrictedFuncDef());
            } else if (token.text().equals("struct")) {
                program.add(parseStructType());
            } else if (token.text().equals("enum")) {
                program.add(parseEnumType());
            } else if (token.text().equals("protocol")) {
                program.add(parseProtocolDecl());
            } else {
                program.add(parseFuncDef());
            }
        }
    }

    public static HmSymbol anything = new HmSymbol();

    public static boolean isWildcardKind(Object kind) {
        if (kind instanceof HmSymbol) {
            return true;
        }
        return false;
    }

    public static boolean isOriginKind(String originKind) {
        switch (originKind) {
            case "native":
            case "user":
                return true;
            default:
                return false;
        }
    }

    public static Block parseBlock() {
        LexerToken origin = consume(List.of("{"));
        Block block = parseBlockBody(List.of("}"));
        consume(Arrays.asList("}"));
        return block;
    }

    public static FuncParameter parseParameter() {
        HmType type = parseType();
        LexerToken name = tryConsumeKind("identifier");
        return new FuncParameter(type.origin, name == null ? null : name.text(), type);
    }

    public static List<FuncParameter> parseParameters() {
        consume(List.of("("));
        List<FuncParameter> parameters = new ArrayList<>();
        while (test(List.of(")")) == null) {
            parameters.add(parseParameter());
            if (tryConsume(List.of(",")) == null) {
                break;
            }
        }
        consume(List.of(")"));
        return parameters;
    }

    public static String parseFuncName() {
        if (tryConsume(List.of("operator")) != null) {
            LexerToken token = consume(List.of("+", "-", "*", "/", "%", "^", "&", "|", "<", ">", "<=", ">=", "==", "++", "--", ".", "~", "<<", ">>", "["));
            if (token.text().equals("&")) {
                if (tryConsume(List.of("[")) != null) {
                    consume(List.of("]"));
                    return "operator&[]";
                }
                if (tryConsume(List.of(".")) != null) {
                    return "operator&." + consumeKind("identifier").text();
                }
                return "operator&";
            }
            if (token.text().equals(".")) {
                String result = "operator." + consumeKind("identifier").text();
                if (tryConsume(List.of("=")) != null) {
                    result += "=";
                }
                return result;
            }
            if (token.text().equals("[")) {
                consume(List.of("]"));
                String result = "operator[]";
                if (tryConsume(List.of("=")) != null) {
                    result += "=";
                }
                return result;
            }
            return "operator" + token.text();
        }
        return consumeKind("identifier").text();
    }


    public static ProtocolFuncDecl parseProtocolFuncDecl() {
        Func func1 = parseFuncDecl();
        return new ProtocolFuncDecl(func1.origin, func1.name(), func1.returnType(), func1.typeParameters(), func1.parameters(), func1.isCast(), func1.shaderType());
    }

    public static Func parseFuncDecl() {
        LexerToken origin;
        HmType returnType;
        String name;
        List<Node> typeParameters;
        Boolean isCast;
        Object shaderType = null;
        LexerToken operatorToken = tryConsume(List.of("operator"));
        if (operatorToken != null) {
            origin = operatorToken;
            typeParameters = parseTypeParameters();
            returnType = parseType();
            name = "operator cast";
            isCast = true;
        } else {
            shaderType = tryConsume(List.of("vertex", "fragment"));
            returnType = parseType();
            if (shaderType != null) {
                LexerToken shaderT = (LexerToken) shaderType;
                origin = shaderT;
                shaderType = ((LexerToken) shaderType).text();
            } else {
                origin = returnType.origin;
            }
            name = parseFuncName();
            typeParameters = parseTypeParameters();
            isCast = false;
        }
        List<FuncParameter> parameters = parseParameters();
        return new Func(origin, name, returnType, typeParameters, parameters, isCast, (String) shaderType);
    }

    public static Node parseProtocolDecl() {
        LexerToken origin = consume(List.of("protocol"));
        String name = consumeKind("identifier").text();
        ProtocolDecl result = new ProtocolDecl(origin, name);
        if (tryConsume(List.of(":")) != null) {
            while (test(List.of("{")) == null) {
                result.addExtends(parseProtocolRef());
                if (tryConsume(List.of(",")) == null) {
                    break;
                }
            }
        }
        consume(List.of("{"));
        while (tryConsume(List.of("}")) == null) {
            result.add(parseProtocolFuncDecl());
            consume(List.of(";"));
        }
        return result;
    }

    public static Field parseField() {
        HmType type = parseType();
        LexerToken name = consumeKind("identifier");
        consume(List.of(";"));
        return new Field(name, name.text(), type);
    }

    public static Node parseStructType() {
        LexerToken origin = consume(List.of("struct"));
        String name = consumeKind("identifier").text();
        List<Node> typeParameters = parseTypeParameters();
        StructType result = new StructType(origin, name, typeParameters);
        consume(List.of("{"));
        while (tryConsume(List.of("}")) == null) {
            result.add(parseField());
        }
        return result;
    }

    public static NativeFunc parseNativeFunc() {
        Func func1 = parseFuncDecl();
        consume(List.of(";"));
        return new NativeFunc(func1.origin, func1.name(), func1.returnType(), func1.typeParameters(), func1.parameters(), func1.isCast(), func1.shaderType());
    }

    public static Node parseNative() {
        LexerToken origin = consume(List.of("native"));
        if (tryConsume(List.of("typedef")) != null) {
            LexerToken name = consumeKind("identifier");
            List<Node> parameters = parseTypeParameters();
            consume(List.of(";"));
            return new NativeType(origin, name.text(), parameters);
        }
        return parseNativeFunc();
    }

    public static Func parseRestrictedFuncDef() {
        consume(List.of("restricted"));
        Func result;
        if (tryConsume(List.of("native")) != null) {
            result = parseNativeFunc();
        } else {
            result = parseFuncDef();
        }
        result.isRestricted = true;
        return result;
    }

    public static EnumMember parseEnumMember() {
        LexerToken name = consumeKind("identifier");
        Expression value = null;
        if (tryConsume(List.of("=")) != null) {
            value = parseConstexpr();
        }
        return new EnumMember(name, name.text(), value);
    }

    public static EnumType parseEnumType() {
        consume(List.of("enum"));
        LexerToken name = consumeKind("identifier");
        HmType baseType;
        if (tryConsume(List.of(":")) != null) {
            baseType = parseType();
        } else {
            baseType = new TypeRef(name, "int", new ArrayList<>());
        }
        consume(List.of("{"));
        EnumType result = new EnumType(name, name.text(), baseType);
        while (test(List.of("}")) == null) {
            result.add(parseEnumMember());
            if (tryConsume(List.of(",")) == null) {
                break;
            }
        }
        consume(List.of("}"));
        return result;
    }

    public static NameResolver createNameResolver(Program program) {
        return new NameResolver(program.globalNameContext());
    }

    public static void resolveNamesInTypes(Program program, NameResolver nameResolver) {
        for (HmType type : program.types().values()) {
            nameResolver.doStatement(type);
        }
    }

    public static void resolveNamesInProtocols(Program program, NameResolver nameResolver) {
        for (HmProtocol proto : program.protocols().values()) {
            nameResolver.doStatement((ProtocolDecl) proto);
        }
    }

    public static void resolveNamesInFunctions(Program program, NameResolver nameResolver) {
        for (List<Func> funcs : program.functions().values()) {
            for (Func func1 : funcs) {
                nameResolver.doStatement(func1);
            }
        }
    }

    public static void resolveProperties(Program program) {
        program.visit(new PropertyResolver());
    }

    public static void resolveTypeDefsInTypes(Program program) {
        TypeDefResolver resolver = new TypeDefResolver();
        for (HmType type : program.types().values()) {
            type.visit(resolver);
        }
    }

    public static void resolveTypeDefsInProtocols(Program program) {
        TypeDefResolver resolver = new TypeDefResolver();
        for (HmProtocol proto : program.protocols().values()) {
            proto.visit(resolver);
        }
    }

    public static void resolveTypeDefsInFunctions(Program program) {
        TypeDefResolver resolver = new TypeDefResolver();
        for (List<Func> funcs : program.functions().values()) {
            for (Func func1 : funcs) {
                func1.visit(resolver);
            }
        }
    }

    // NOTE: The next line is line 28, and we rely on this in Prepare.js.
    public static String standardLibrary = """

// This is the WSL standard library. Implementations of all of these things are in
// Intrinsics.js.

// Need to bootstrap void first.
native typedef void;

native typedef uint8;
native typedef int32;
native typedef uint32;
native typedef bool;
typedef int = int32;
typedef uint = uint32;

native typedef float32;
native typedef float64;
typedef float = float32;
typedef double = float64;

native operator int32(uint32);
native operator int32(uint8);
native operator int32(float);
native operator int32(double);
native operator uint32(int32);
native operator uint32(uint8);
native operator uint32(float);
native operator uint32(double);
native operator uint8(int32);
native operator uint8(uint32);
native operator uint8(float);
native operator uint8(double);
native operator float(int32);
native operator float(uint32);
native operator float(uint8);
native operator float(double);
native operator double(float);
native operator double(int32);
native operator double(uint32);
native operator double(uint8);

native int operator+(int, int);
native uint operator+(uint, uint);
uint8 operator+(uint8 a, uint8 b) { return uint8(uint(a) + uint(b)); }
native float operator+(float, float);
native double operator+(double, double);
int operator++(int value) { return value + 1; }
uint operator++(uint value) { return value + 1; }
uint8 operator++(uint8 value) { return value + 1; }
native int operator-(int, int);
native uint operator-(uint, uint);
uint8 operator-(uint8 a, uint8 b) { return uint8(uint(a) - uint(b)); }
native float operator-(float, float);
native double operator-(double, double);
int operator--(int value) { return value - 1; }
uint operator--(uint value) { return value - 1; }
uint8 operator--(uint8 value) { return value - 1; }
native int operator*(int, int);
native uint operator*(uint, uint);
uint8 operator*(uint8 a, uint8 b) { return uint8(uint(a) * uint(b)); }
native float operator*(float, float);
native double operator*(double, double);
native int operator/(int, int);
native uint operator/(uint, uint);
uint8 operator/(uint8 a, uint8 b) { return uint8(uint(a) / uint(b)); }
native int operator&(int, int);
native int operator|(int, int);
native int operator^(int, int);
native int operator~(int);
native int operator<<(int, uint);
native int operator>>(int, uint);
native uint operator&(uint, uint);
native uint operator|(uint, uint);
native uint operator^(uint, uint);
native uint operator~(uint);
native uint operator<<(uint, uint);
native uint operator>>(uint, uint);
uint8 operator&(uint8 a, uint8 b) { return uint8(uint(a) & uint(b)); }
uint8 operator|(uint8 a, uint8 b) { return uint8(uint(a) | uint(b)); }
uint8 operator^(uint8 a, uint8 b) { return uint8(uint(a) ^ uint(b)); }
uint8 operator~(uint8 value) { return uint8(~uint(value)); }
uint8 operator<<(uint8 a, uint b) { return uint8(uint(a) << (b & 7)); }
uint8 operator>>(uint8 a, uint b) { return uint8(uint(a) >> (b & 7)); }
native float operator/(float, float);
native double operator/(double, double);
native bool operator==(int, int);
native bool operator==(uint, uint);
bool operator==(uint8 a, uint8 b) { return uint(a) == uint(b); }
native bool operator==(bool, bool);
native bool operator==(float, float);
native bool operator==(double, double);
native bool operator<(int, int);
native bool operator<(uint, uint);
bool operator<(uint8 a, uint8 b) { return uint(a) < uint(b); }
native bool operator<(float, float);
native bool operator<(double, double);
native bool operator<=(int, int);
native bool operator<=(uint, uint);
bool operator<=(uint8 a, uint8 b) { return uint(a) <= uint(b); }
native bool operator<=(float, float);
native bool operator<=(double, double);
native bool operator>(int, int);
native bool operator>(uint, uint);
bool operator>(uint8 a, uint8 b) { return uint(a) > uint(b); }
native bool operator>(float, float);
native bool operator>(double, double);
native bool operator>=(int, int);
native bool operator>=(uint, uint);
bool operator>=(uint8 a, uint8 b) { return uint(a) >= uint(b); }
native bool operator>=(float, float);
native bool operator>=(double, double);

bool operator&(bool a, bool b)
{
    if (a)
        return b;
    return false;
}

bool operator|(bool a, bool b)
{
    if (a)
        return true;
    if (b)
        return true;
    return false;
}

bool operator^(bool a, bool b)
{
    if (a)
        return !b;
    return b;
}

bool operator~(bool value)
{
    return !value;
}

protocol Addable {
    Addable operator+(Addable, Addable);
}

protocol Equatable {
    bool operator==(Equatable, Equatable);
}

restricted operator<T> T()
{
    T defaultValue;
    return defaultValue;
}

restricted operator<T> T(T x)
{
    return x;
}

operator<T:Equatable> bool(T x)
{
    return x != T();
}

struct vec2<T> {
    T x;
    T y;
}

typedef int2 = vec2<int>;
typedef uint2 = vec2<uint>;
typedef float2 = vec2<float>;
typedef double2 = vec2<double>;

operator<T> vec2<T>(T x, T y)
{
    vec2<T> result;
    result.x = x;
    result.y = y;
    return result;
}

bool operator==<T:Equatable>(vec2<T> a, vec2<T> b)
{
    return a.x == b.x && a.y == b.y;
}

thread T* operator&[]<T>(thread vec2<T>* foo, uint index)
{
    if (index == 0)
        return &foo->x;
    if (index == 1)
        return &foo->y;
    trap;
}

struct vec3<T> {
    T x;
    T y;
    T z;
}

typedef int3 = vec3<int>;
typedef uint3 = vec3<uint>;
typedef float3 = vec3<float>;
typedef double3 = vec3<double>;

operator<T> vec3<T>(T x, T y, T z)
{
    vec3<T> result;
    result.x = x;
    result.y = y;
    result.z = z;
    return result;
}

operator<T> vec3<T>(vec2<T> v2, T z)
{
    vec3<T> result;
    result.x = v2.x;
    result.y = v2.y;
    result.z = z;
    return result;
}

operator<T> vec3<T>(T x, vec2<T> v2)
{
    vec3<T> result;
    result.x = x;
    result.y = v2.x;
    result.z = v2.y;
    return result;
}

bool operator==<T:Equatable>(vec3<T> a, vec3<T> b)
{
    return a.x == b.x && a.y == b.y && a.z == b.z;
}

thread T* operator&[]<T>(thread vec3<T>* foo, uint index)
{
    if (index == 0)
        return &foo->x;
    if (index == 1)
        return &foo->y;
    if (index == 2)
        return &foo->z;
    trap;
}

struct vec4<T> {
    T x;
    T y;
    T z;
    T w;
}

typedef int4 = vec4<int>;
typedef uint4 = vec4<uint>;
typedef float4 = vec4<float>;
typedef double4 = vec4<double>;

operator<T> vec4<T>(T x, T y, T z, T w)
{
    vec4<T> result;
    result.x = x;
    result.y = y;
    result.z = z;
    result.w = w;
    return result;
}

operator<T> vec4<T>(vec2<T> v2, T z, T w)
{
    vec4<T> result;
    result.x = v2.x;
    result.y = v2.y;
    result.z = z;
    result.w = w;
    return result;
}

operator<T> vec4<T>(T x, vec2<T> v2, T w)
{
    vec4<T> result;
    result.x = x;
    result.y = v2.x;
    result.z = v2.y;
    result.w = w;
    return result;
}

operator<T> vec4<T>(T x, T y, vec2<T> v2)
{
    vec4<T> result;
    result.x = x;
    result.y = y;
    result.z = v2.x;
    result.w = v2.y;
    return result;
}

operator<T> vec4<T>(vec2<T> v2a, vec2<T> v2b)
{
    vec4<T> result;
    result.x = v2a.x;
    result.y = v2a.y;
    result.z = v2b.x;
    result.w = v2b.y;
    return result;
}

operator<T> vec4<T>(vec3<T> v3, T w)
{
    vec4<T> result;
    result.x = v3.x;
    result.y = v3.y;
    result.z = v3.z;
    result.w = w;
    return result;
}

operator<T> vec4<T>(T x, vec3<T> v3)
{
    vec4<T> result;
    result.x = x;
    result.y = v3.x;
    result.z = v3.y;
    result.w = v3.z;
    return result;
}

bool operator==<T:Equatable>(vec4<T> a, vec4<T> b)
{
    return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w;
}

thread T* operator&[]<T>(thread vec4<T>* foo, uint index)
{
    if (index == 0)
        return &foo->x;
    if (index == 1)
        return &foo->y;
    if (index == 2)
        return &foo->z;
    if (index == 3)
        return &foo->w;
    trap;
}

native thread T* operator&[]<T>(thread T[], uint);
native threadgroup T* operator&[]<T>(threadgroup T[], uint);
native device T* operator&[]<T>(device T[], uint);
native constant T* operator&[]<T>(constant T[], uint);

native uint operator.length<T>(thread T[]);
native uint operator.length<T>(threadgroup T[]);
native uint operator.length<T>(device T[]);
native uint operator.length<T>(constant T[]);

uint operator.length<T, uint length>(T[length])
{
    return length;
}
""";

    public static String intToString(int x) {
        switch (x) {
            case 0:
                return "x";
            case 1:
                return "y";
            case Constants.WSL_2:
                return "z";
            case Constants.WSL_3:
                return "w";
            default:
                throw new Error("Could not generate standard library.");
        }
    }

    // There are 481 swizzle operators. Let's not list them explicitly.
    public static String generateSwizzle(int maxDepth, int maxItems, ArrayList<String> array) {
        ArrayList<String> newArray = array != null ? array : new ArrayList<String>();
        if (newArray.size() == maxDepth) {
            String result = "vec" + newArray.size() + "<T> operator." + String.join("", newArray) + "<T>(vec" + maxItems + "<T> v)\n{\n    vec";
            result += newArray.size() + "<T> result;\n";
            for (int i = 0; i < newArray.size(); i++) {
                result += "    result." + intToString(i) + " = v." + newArray.get(i) + ";\n";
            }
            result += "    return result;\n}\n";
            return result;
        }
        String result = "";
        for (int i = 0; i < maxItems; i++) {
            newArray.add(intToString(i));
            result += generateSwizzle(maxDepth, maxItems, newArray);
            newArray.remove(newArray.size() - 1);
        }
        return result;
    }

    public static void forMaxDepth() {
        for (int maxDepth = Constants.WSL_2; maxDepth <= Constants.WSL_2; maxDepth++) {
            for (int maxItems = Constants.WSL_2; maxItems <= Constants.WSL_2; maxItems++) {
                standardLibrary += generateSwizzle(maxDepth, maxItems, null);
            }
        }
    }

    public static void synthesizeEnumFunctions(Program program) {
        for (Node type : program.types().values()) {
            if (!(type instanceof EnumType)) {
                continue;
            }
            NativeFunc nativeFunc;
            boolean isCast = false;
            String shaderType = null;
            nativeFunc = new NativeFunc(
                    type.origin,
                    "operator==",
                    new TypeRef(type.origin, "bool", new ArrayList<>()),
                    new ArrayList<Node>(),
                    new ArrayList<FuncParameter>(List.of(
                            new FuncParameter(type.origin, null, new TypeRef(type.origin, type.name(), new ArrayList<>())),
                            new FuncParameter(type.origin, null, new TypeRef(type.origin, type.name(), new ArrayList<>())))
                    ),
                    isCast,
                    shaderType
            );

            nativeFunc.implementation = (List<EPtr> arr, CallExpression callExpression) -> EPtr.boxStatic((double) (arr.get(0).loadValue()) == (double) (arr.get(1).loadValue()));
            program.add(nativeFunc);

            nativeFunc = new NativeFunc(
                    type.origin,
                    "operator.value",
                    (HmType) (((EnumType) type).baseType().visit(new Rewriter())),
                    new ArrayList<Node>(),
                    new ArrayList<FuncParameter>(List.of(new FuncParameter(type.origin, null, new TypeRef(type.origin, type.name(), new ArrayList<Node>())))),
                    isCast,
                    shaderType
            );
            nativeFunc.implementation = (List<EPtr> arr, CallExpression callExpression) -> arr.get(0);
            program.add(nativeFunc);

            nativeFunc = new NativeFunc(
                    type.origin,
                    "operator cast",
                    (HmType) ((EnumType) type).baseType().visit(new Rewriter()),
                    new ArrayList<Node>(),
                    new ArrayList<FuncParameter>(List.of(new FuncParameter(type.origin, null, new TypeRef(type.origin, type.name(), new ArrayList<Node>())))),
                    isCast,
                    shaderType
            );
            nativeFunc.implementation = (List<EPtr> arr, CallExpression callExpression) -> arr.get(0);
            program.add(nativeFunc);

            nativeFunc = new NativeFunc(
                    type.origin,
                    "operator cast",
                    new TypeRef(type.origin, type.name(), new ArrayList<Node>()),
                    new LinkedList<Node>(),
                    Arrays.asList(new FuncParameter(type.origin, null, (HmType) (((EnumType) type).baseType().visit(new Rewriter())))),
                    isCast,
                    shaderType
            );
            nativeFunc.implementation = (List<EPtr> arr, CallExpression callExpression) -> arr.get(0);
            program.add(nativeFunc);
        }
    }

    public static List<TypeVariable> createTypeParameters(StructType structType) {
        return (List<TypeVariable>) structType.typeParameters.stream().map((Node typeParameter) -> {
            return (TypeVariable) typeParameter.visit(new TypeParameterRewriter());
        }).collect(Collectors.toList());
    }

    public static List<HmType> createTypeArguments(List<TypeVariable> typeParameters) {
        return typeParameters.stream().map((typeParameter) -> {return (HmType) typeParameter.visit(new AutoWrapper());}).collect(Collectors.toList());
    }

    public static void setupImplementationData(
            StructType structType,
            Field field,
            NativeFunc nativeFunc,
            SetupImplementationDataInterface implementation
    ) {
        nativeFunc.instantiateImplementation = (InstantiationSubstitution substitution) -> {
            Node newType = structType.instantiate(
                    (ArrayList<Node>) nativeFunc.typeParameters().stream().map(((Node typeParameter) -> {
                        Node substitute = substitution.map().get(typeParameter);
                        if (substitute == null) {
                            throw new Error("Null substitute for type parameter " + typeParameter.toString());
                        }
                        return substitute;
                    })).collect(Collectors.toList())
            );
            return new ImplementationDataType((StructType) newType, field.name());
        };
        nativeFunc.visitImplementationData = (ImplementationDataType implementationData, Object visitor) -> {
            // Visiting the type first ensures that the struct layout builder figures out the field's
            // offset.
            implementationData.hmType.visit((Base) visitor);
        };
        nativeFunc.didLayoutStructsInImplementationData = (ImplementationDataType implementationData) -> {
            int structSize = implementationData.hmType.size();
            if (structSize == -1) {
                throw new Error("No struct size for " + nativeFunc.toString());
            }
            Field field1 = implementationData.hmType.fieldByName(implementationData.fieldName);
            if (field1 == null) {
                throw new Error("Could not find field");
            }
            int offset = field1.offset;
            if (offset == -1) {
                throw new Error("No offset for " + nativeFunc.toString());
            }
            int fieldSize = field1.hmType().size();
            if (fieldSize == -1) {
                throw new Error("No field size for " + nativeFunc.toString());
            }
            implementationData.offset = offset;
            implementationData.structSize = structSize;
            implementationData.fieldSize = fieldSize;
        };
        nativeFunc.implementation = (List<EPtr> argumentList, CallExpression node) -> {
            NativeFuncInstance nativeFuncInstance = (NativeFuncInstance) node.nativeFuncInstance;
            ImplementationDataType implementationData = nativeFuncInstance.implementationData();
            return (EPtr) implementation.apply(argumentList, implementationData.offset, implementationData.structSize, implementationData.fieldSize);
        };
    }

    @Override
    public EPtr apply(List<EPtr> bases, int offset, int structSize, int fieldSize) {
        return null;
    }

    @Override
    public Expression apply(LexerToken token, Expression hmLeft, Expression hmRight) {
        return null;
    }

    public static HmType createFieldType(StructType structType, Field field, List<TypeVariable> typeParameters) {
        return (HmType) field.hmType().visit(new Substitution(structType.typeParameters, typeParameters.stream().map((typeParameter) -> (Node) typeParameter).collect(Collectors.toList())));
    }

    public static TypeRef createTypeRef(StructType structType, List<TypeVariable> typeParameters) {
        return TypeRef.instantiate(structType, createTypeArguments(typeParameters));
    }

    public static void setupAnder(
            String addressSpace,
            boolean isCast,
            String shaderType,
            Program program,
            StructType structType,
            Field field,
            List<TypeVariable> typeParameters) {
        typeParameters = createTypeParameters(structType);
        NativeFunc nativeFunc = new NativeFunc(
                field.origin,
                "operator&." + field.name(),
                new PtrType(field.origin, addressSpace, createFieldType(structType, field, typeParameters)),
                typeParameters,
                Arrays.asList(new FuncParameter(field.origin, null, new PtrType(field.origin, addressSpace, createTypeRef(structType, typeParameters)))),
                isCast,
                shaderType
        );
        setupImplementationData(structType, field, nativeFunc, (bases, offset, structSize, fieldSize) -> {
            EPtr base = (EPtr) bases.get(0).loadValue();
            return EPtr.boxStatic(base.plus(offset));
        });
        program.add(nativeFunc);
    }

    public static void synthesizeStructAccessors(Program program) {
        for (HmType type : program.types().values()) {
            if (!(type instanceof StructType)) {
                continue;
            }
            StructType structType = (StructType) type;
            for (Field field : structType.fields()) {
                boolean isCast = false;
                String shaderType = null;
                List<TypeVariable> typeParameters;
                NativeFunc nativeFunc;

                // The getter: operator.field
                typeParameters = AllFunction.createTypeParameters(structType);
                nativeFunc = new NativeFunc(
                        field.origin,
                        "operator." + field.name(),
                        createFieldType(structType, field, typeParameters),
                        typeParameters,
                        Arrays.asList(new FuncParameter(field.origin, null, createTypeRef(structType, typeParameters))),
                        isCast,
                        shaderType
                );
                setupImplementationData(structType, field, nativeFunc, (bases, offset, structSize, fieldSize) -> {
                    EPtr result = new EPtr(new EBuffer(fieldSize), 0);
                    result.copyFrom(bases.get(0).plus(offset), fieldSize);
                    return result;
                });
                program.add(nativeFunc);

                // The setter: operator.field=
                typeParameters = AllFunction.createTypeParameters(structType);
                nativeFunc = new NativeFunc(
                        field.origin,
                        "operator." + field.name() + "=",
                        createTypeRef(structType, typeParameters),
                        typeParameters,
                        Arrays.asList(
                                new FuncParameter(field.origin, null, createTypeRef(structType, typeParameters)),
                                new FuncParameter(field.origin, null, createFieldType(structType, field, typeParameters))
                        ),
                        isCast,
                        shaderType
                );
                setupImplementationData(structType, field, nativeFunc, (bases, offset, structSize, fieldSize) -> {
                    EPtr base = bases.get(0);
                    EPtr value = bases.get(1);
                    EPtr result = new EPtr(new EBuffer(structSize), 0);
                    result.copyFrom(base, structSize);
                    result.plus(offset).copyFrom(value, fieldSize);
                    return result;
                });
                program.add(nativeFunc);

                setupAnder("thread", isCast, shaderType, program, structType, field, typeParameters);
                setupAnder("threadgroup", isCast, shaderType, program, structType, field, typeParameters);
                setupAnder("device", isCast, shaderType, program, structType, field, typeParameters);
                setupAnder("constant", isCast, shaderType, program, structType, field, typeParameters);
            }
        }
    }

    public static ConfigType intConfigType = new ConfigType("int", (UnificationContext unificationContext, GenericLiteralType type) -> {
        HmType realThis = (HmType) unificationContext.find(type);
        if (!realThis.isNumber()) {
            return new VerifyResult(false, "Cannot use int literal with non-number type " + realThis.toString());
        }
        if (!realThis.canRepresent.apply(type.value())) {
            return new VerifyResult(false, "Int literal " + type.value() + " too large to be represented by type " + realThis.toString());
        }
        return new VerifyResult(true);
    }
    );
    public static Config intConfig = new Config(
            "IntLiteral",
            "int",
            (LexerToken origin, Double value) -> {
                return new IntLiteral(origin, (-1 * value.intValue()) | 0);
    }, (LexerToken origin, Double value) -> {
        return new IntLiteralType(origin, value.intValue());
    }
    );
    public static Config uintConfig = new Config(
            "UintLiteral",
            "uint",
            (LexerToken origin, Double value) -> {
                return new UintLiteral(origin, (long) ((int) (-1 * value)) >>> 0);
    }, (LexerToken origin, Double value) -> {
        return (new UintLiteralType(origin, value));
    }
    );

    public static ConfigType uintConfigType = new ConfigType("uint", (UnificationContext unificationContext, GenericLiteralType type) -> {
        NativeType realThis = (NativeType) unificationContext.find(type);
        if (!realThis.isInt()) {
            return new VerifyResult(false, "Cannot use uint literal with non-integer type " + realThis.toString());
        }
        if (realThis.isSigned) {
            return new VerifyResult(false, "Cannot use uint literal with signed type " + realThis.toString());
        }
        if (!realThis.canRepresent.apply(type.value())) {
            return new VerifyResult(false, "Uint literal " + type.value() + " too large to be represented by type " + realThis.toString());
        }
        return new VerifyResult(true);
    });


    public static Program doPrep(String code) {
        return prepare("/internal/test", 0, code);
    }

    public static List<LexerToken> doLex(String code) {
        Lexer lexer = new Lexer("/internal/test", "native", 0, code);
        List<LexerToken> result = new ArrayList<LexerToken>();
        while (true) {
            LexerToken next = lexer.next();
            if (next == null) {
                return result;
            }
            result.add(next);
        }
    }

    public static TypedValue makeInt(Program program, int value) {
        return TypedValue.box(program.intrinsics.int32, value);
    }

    public static TypedValue makeUint(Program program, int value) {
        return TypedValue.box(program.intrinsics.uint32, value);
    }

    public static TypedValue makeUint8(Program program, int value) {
        return TypedValue.box(program.intrinsics.uint8, value);
    }

    public static TypedValue makeBool(Program program, boolean value) {
        return TypedValue.box(program.intrinsics.bool, value);
    }

    public static TypedValue makeFloat(Program program, float value) {
        return TypedValue.box(program.intrinsics.hmFloat, value);
    }

    public static TypedValue makeDouble(Program program, double value) {
        return TypedValue.box(program.intrinsics.hmDouble, value);
    }

    public static TypedValue makeEnum(Program program, String enumName, String value) {
        EnumType enumType = (EnumType) program.types().get(enumName);
        if (enumType == null) {
            throw new Error("No type named " + enumName);
        }
        EnumMember enumMember = enumType.memberByName(value);
        if (enumMember == null) {
            throw new Error("No member named " + enumName + " in " + enumType.toString());
        }
        return TypedValue.box(enumType, ((GenericLiteral) (enumMember.value.unifyNode())).valueForSelectedType());
    }

    public static void checkNumber(Program program, TypedValue result, int expected) {
        if (!((NativeType) (result.hmType().unifyNode())).isNumber()) {
            throw new Error("Wrong result type; result: " + result);
        }
        if (WSLNumber.objectToInt(result.value()) != expected) {
            throw new Error("Wrong result: " + result.value() + " (expected " + expected + ")");
        }
    }

    public static void checkInt(Program program, TypedValue result, int expected) {
        if (result.hmType().equals(program.intrinsics.int32) == null) {
            throw new Error("Wrong result type; result: " + result);
        }
        checkNumber(program, result, expected);
    }

    public static void checkEnum(Program program, TypedValue result, int expected) {
        if (!(result.hmType().unifyNode() instanceof EnumType)) {
            throw new Error("Wrong result type; result: " + result);
        }
        if ((double) result.value() != expected) {
            throw new Error("Wrong result: " + result.value() + " (expected " + expected + ")");
        }
    }

    public static void checkUint(Program program, TypedValue result, int expected) {
        if (result.hmType().equals(program.intrinsics.uint32) == null) {
            throw new Error("Wrong result type: " + result.hmType().toString());
        }
        if (WSLNumber.objectToInt(result.value()) != expected) {
            throw new Error("Wrong result: " + result.value() + " (expected " + expected + ")");
        }
    }

    public static void checkUint8(Program program, TypedValue result, int expected) {
        if (result.hmType().equals(program.intrinsics.uint8) == null) {
            throw new Error("Wrong result type: " + result.hmType().toString());
        }
        if (WSLNumber.objectToInt(result.value()) != expected) {
            throw new Error("Wrong result: " + result.value() + " (expected " + expected + ")");
        }
    }

    public static void checkBool(Program program, TypedValue result, boolean expected) {
        if (result.hmType().equals(program.intrinsics.bool) == null) {
            throw new Error("Wrong result type: ${result.hmType}");
        }
        if ((boolean) result.value() != expected) {
            throw new Error("Wrong result: " + result.value() + " (expected " + expected + ")");
        }
    }

    public static void checkFloat(Program program, TypedValue result, float expected) {
        if (result.hmType().equals(program.intrinsics.hmFloat) == null) {
            throw new Error("Wrong result type: " + result.hmType().toString());
        }
        if (WSLNumber.objectToFloat(result.value()) != expected) {
            throw new Error("Wrong result: " + result.value() + " (expected " + expected + ")");
        }
    }

    public static void checkDouble(Program program, TypedValue result, double expected) {
        if (result.hmType().equals(program.intrinsics.hmDouble) == null) {
            throw new Error("Wrong result type: ${result.hmType}");
        }
        if (!Objects.equals(result.value(), expected)) {
            throw new Error("Wrong result: " + result.value () + "expected: :" + expected);
        }
    }

    public static void checkLexerToken(LexerToken result, int expectedIndex, String expectedKind, String expectedText) {
        if (result.index() != expectedIndex) {
            throw new Error("Wrong lexer index; result: " + result.index() + " (expected " + expectedIndex + ")");
        }
        if (!Objects.equals(result.kind(), expectedKind)) {
            throw new Error("Wrong lexer kind; result: " + result.kind() + " (expected " + expectedKind + ")");
        }
        if (!result.text().equals(expectedText)) {
            throw new Error("Wrong lexer text; result: " + result.text() + " (expected " + expectedText + ")");
        }
    }

    public static void checkFail(Supplier<Object> callback, Function<RuntimeException, Boolean> predicate) {
        try {
            callback.get();
            throw new Error("Did not throw exception");
        } catch (RuntimeException e) {
            if (predicate.apply(e)) {
                return;
            }
            throw e;
        }
    }
    public static TypedValue callFunction(Program program, String
            name, ArrayList<Node> typeArguments, ArrayList<TypedValue> argumentList) {
        ArrayList<HmType> argumentTypes = (ArrayList<HmType>) argumentList.stream().map(($0) -> $0.hmType()).collect(Collectors.toList());
        Object funcOrFailures = resolveInlinedFunction(program, name, typeArguments, argumentTypes, true);
        if (!(funcOrFailures instanceof Func)) {
            ArrayList<OverloadResolutionFailure> failures = (ArrayList<OverloadResolutionFailure>) funcOrFailures;
            List<String> arr = failures.stream().map(OverloadResolutionFailure::toString).collect(Collectors.toList());
            throw new WTypeError("<callFunction>" + "Cannot resolve function call " + name + "<" + typeArguments + ">(" + argumentList + ")" + (failures.size() != 0 ? "; tried:\n" + arr.stream().map(($0) -> $0.toString()).collect(Collectors.joining("\n")) : ""));
        }
        Func func1 = (Func) funcOrFailures;
        for (int i = 0; i < func1.parameters().size(); i++) {
            HmType type = argumentTypes.get(i).instantiatedType();
            type.visit(new StructLayoutBuilder());
            func1.parameters().get(i).ePtr().copyFrom(argumentList.get(i).ePtr(), type.size());
        }

        Evaluator evaluator = new Evaluator(program);

        EPtr result = evaluator.runFunc((FuncDef) func1);

        return new TypedValue(func1.uninstantiatedReturnType, result);
    }


    public static FuncDef parseFuncDef() {
        Func func1 = parseFuncDecl();
        Block body = parseBlock();
        return new FuncDef(func1.origin, func1.name(), func1.returnType(), func1.typeParameters(), func1.parameters(), body, func1.isCast(), func1.shaderType());
    }

    public static void check(Program program) {
        program.visit(new Checker(program));
    }

    public static void checkLiteralTypes(Program program) {
        program.visit(new LiteralTypeChecker());
    }

    public static void checkLoops(Program program) {
        program.visit(new LoopChecker());
    }

    public static void checkRecursion(Program program) {
        program.visit(new RecursionChecker());
    }

    public static void checkRecursiveTypes(Program program) {
        program.visit(new RecursiveTypeChecker());
    }

    public static void checkReturns(Program program) {
        program.visit(new ReturnChecker(program));
    }

    public static void checkUnreachableCode(Program program) {
        program.visit(new UnreachableCodeChecker(program));
    }

    public static void checkExpressionWrapped(Node node) {
        node.visit(new WrapChecker(node));
    }

    public static void checkProgramWrapped(Node node) {
        node.visit(new ExpressionFinder(AllFunction::checkExpressionWrapped));
    }

    public static Program cloneProgram(Program program) {
        Program result = new Program();
        StatementCloner cloner = new StatementCloner();
        for (Node statement : program.topLevelStatements()) {
            result.add((Node) statement.visit(cloner));
        }
        return result;
    }


    //8571
    public static void foldConstexprs(Program program) {
        program.visit(new ConstexprFolder());
    }

    public static void findHighZombies(Program program) {
        program.visit(new HighZombieFinder());
    }

    public static void flattenProtocolExtends(Program program) {
        VisitingSet visiting = new VisitingSet();
        for (HmProtocol protocol1 : program.protocols().values()) {
            flatten((ProtocolDecl) protocol1, visiting);
        }
    }

    public static Program programWithUnnecessaryThingsRemoved(Program program) {
        NameFinder nameFinder = new NameFinder();
        // Build our roots.
        for (Node statement : program.topLevelStatements()) {
            if (statement.origin.originKind().equals("user")) {
                nameFinder.add(statement.name());
            }
        }

        // Unfortunately, we cannot know yet which operator casts we'll need.
        nameFinder.add("operator cast");

        // We need these even if the program doesn't mention them by name.
        nameFinder.add("void");
        nameFinder.add("bool");
        nameFinder.add("int");

        // Pull in things as necessary.
        while (nameFinder.workList.size() > 0) {
            String name = nameFinder.workList.remove(nameFinder.workList.size() - 1);
            List<? extends Node> tmp = program.globalNameContext().underlyingThings(anything, name);
            for (Node thing : tmp) {
                thing.visit(nameFinder);
            }
        }

        Program result = new Program();
        for (String name : nameFinder.set) {
            for (Node thing : program.globalNameContext().underlyingThings(anything, name)) {
                result.add(thing);
            }
        }

        return result;
    }

    public static void flatten(ProtocolDecl protocol1, VisitingSet visiting) {
        if (protocol1.extend.size() == 0) {
            return;
        }
        visiting.doVisit(protocol1, () -> {
            for (ProtocolRef rawParent : protocol1.extend) {
                ProtocolDecl parent = rawParent.protocolDecl;
                flatten(parent, visiting);
                for (ProtocolFuncDecl signature : parent.signatures()) {
                    Object newSignature = signature.visit(new Substitution(Arrays.asList(parent.typeVariable()), Arrays.asList(protocol1.typeVariable())));
                    protocol1.add((ProtocolFuncDecl) newSignature);
                }
            }
            protocol1.extend = new ArrayList<>();
            return null;
        });
    }

    public static Config doubleConfig = new Config(
            "DoubleLiteral",
            "double",
            (origin, value) -> {
                return new IntLiteral(origin, -1 * value.intValue());
            },
            (origin, value) -> {
                return new DoubleLiteralType(origin, value);
            }
    );

    public static ConfigType doubleConfigType = new ConfigType("double", (UnificationContext unificationContext, GenericLiteralType type) -> {
        HmType realThis = (HmType) unificationContext.find(type);
        if (!realThis.isFloating()) {
            return new VerifyResult(false, "Cannot use double literal with non-floating type " + realThis);
        }
        if (!realThis.canRepresent.apply(type.value())) {
            return new VerifyResult(false, "Float literal " + type.value() + " does not fit in type " + realThis);
        }
        return new VerifyResult(true);
    });

    public static Config floatConfig = new Config(
            "FloatLiteral",
            "float",
            (origin, value) -> {
                return new IntLiteral(origin, -1 * value.intValue());
            },
            (origin, value) -> {
                return new FloatLiteralType(origin, value);
            }
    );

    public static ConfigType floatConfigType = new ConfigType("float", (UnificationContext unificationContext, GenericLiteralType type) -> {
        HmType realThis = (HmType) unificationContext.find(type);
        if (!realThis.isFloating()) {
            return new VerifyResult(false, "Cannot use float literal with non-floating type " + realThis);
        }
        if (!realThis.canRepresent.apply(type.value())) {
            return new VerifyResult(false, "Float literal " + type.value() + " does not fit in type " + realThis);
        }
        return new VerifyResult(true);
    });

    public static boolean isAddressSpace(String addressSpace) {
        switch (addressSpace) {
            case "constant":
                return true;
            case "device":
                return true;
            case "threadgroup":
                return true;
            case "thread":
                return true;
            default:
                return false;
        }
    }

    public static void validateAddressSpace(String addressSpace) {
        if (!isAddressSpace(addressSpace)) {
            throw new Error("Bad address space: " + addressSpace);
        }
    }

    @Override
    public Object apply(ArrayList<ArrayList<String>> array, Function<List<List<String>>, Object> callback, List<List<String>> order) {
        return null;
    }
}

class WSLNumber {
    public static int objectToInt(Object value) {
        if (value instanceof Number newValue) {
            return newValue.intValue();
        } else {
            return 0;
        }
    }

    public static long objectToLong(Object value) {
        if (value instanceof Number) {
            Number newValue = (Number) value;
            return newValue.longValue();
        } else {
            return 0L;
        }
    }

    public static float objectToFloat(Object value) {
        if (value instanceof Number) {
            Number newValue = (Number) value;
            return newValue.floatValue();
        } else {
            return 0.0f;
        }
    }

    public static double objectToDouble(Object value) {
        if (value instanceof Number) {
            Number newValue = (Number) value;
            return newValue.doubleValue();
        } else {
            return 0.0;
        }
    }

    public static boolean objectToBoolean(Object value) {
        if (value instanceof Boolean) {
            return (Boolean) value;
        }
        return false;
    }
}

interface SetupImplementationDataInterface {
    EPtr apply(List<EPtr> bases, int offset, int structSize, int fieldSize);
}

interface NullTypeVariableUnifyInterface {
    Object apply(ArrayList<ArrayList<String>> array, Function<List<List<String>>, Object> callback, List<List<String>> order);
}

interface GenericParseLeftInterface {
    Expression apply(LexerToken token, Expression hmLeft, Expression hmRight);
}

class GetTest {

    public Map<String, Supplier<Void>> tests = new HashMap<>();

    public Map<String, Supplier<Void>> getTests() {

        tests.put("literalBool", () -> {
            Program program = AllFunction.doPrep("bool foo() { return true; }");
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), true);
            return null;
        });

        tests.put("identityBool", () -> {
            Program program = AllFunction.doPrep("bool foo(bool x) { return x; }");
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, true)))), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, false)))), false);
            return null;
        });

        tests.put("intSimpleMath", () -> {
            Program program = AllFunction.doPrep("int foo(int x, int y) { return x + y; }");
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>( Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7), AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_12);

            program = AllFunction.doPrep("int foo(int x, int y) { return x - y; }");
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7), AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_2);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5), AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSLNEGATIVE_2);

            program = AllFunction.doPrep("int foo(int x, int y) { return x * y; }");
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7), AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_35);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7), AllFunction.makeInt(program, Constants.WSLNEGATIVE_5)))), Constants.WSLNEGATIVE_35);

            program = AllFunction.doPrep("int foo(int x, int y) { return x / y; }");
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7), AllFunction.makeInt(program, Constants.WSL_2)))), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7), AllFunction.makeInt(program, Constants.WSLNEGATIVE_2)))), Constants.WSLNEGATIVE_3);
            return null;
        });

        tests.put("uintSimpleMath", () -> {
            Program program = AllFunction.doPrep("uint foo(uint x, uint y) { return x + y; }");
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, Constants.WSL_7), AllFunction.makeUint(program, Constants.WSL_5)))), Constants.WSL_12);

            program = AllFunction.doPrep("uint foo(uint x, uint y) { return x - y; }");
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, Constants.WSL_7), AllFunction.makeUint(program, Constants.WSL_5)))), Constants.WSL_2);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, Constants.WSL_5), AllFunction.makeUint(program, Constants.WSL_7)))), Constants.WSL_4294967294);

            program = AllFunction.doPrep("uint foo(uint x, uint y) { return x * y; }");
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, Constants.WSL_7), AllFunction.makeUint(program, Constants.WSL_5)))), Constants.WSL_35);

            program = AllFunction.doPrep("uint foo(uint x, uint y) { return x / y; }");
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, Constants.WSL_7), AllFunction.makeUint(program, Constants.WSL_2)))), Constants.WSL_3);
            return null;
        });

        tests.put("uint8SimpleMath", () -> {
            Program program = AllFunction.doPrep("uint8 foo(uint8 x, uint8 y) { return x + y; }");
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, Constants.WSL_7), AllFunction.makeUint8(program, Constants.WSL_5)))), Constants.WSL_12);

            program = AllFunction.doPrep("uint8 foo(uint8 x, uint8 y) { return x - y; }");
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, Constants.WSL_7), AllFunction.makeUint8(program, Constants.WSL_5)))), Constants.WSL_2);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, Constants.WSL_5), AllFunction.makeUint8(program, Constants.WSL_7)))), Constants.WSL_254);

            program = AllFunction.doPrep("uint8 foo(uint8 x, uint8 y) { return x * y; }");
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, Constants.WSL_7), AllFunction.makeUint8(program, Constants.WSL_5)))), Constants.WSL_35);

            program = AllFunction.doPrep("uint8 foo(uint8 x, uint8 y) { return x / y; }");
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, Constants.WSL_7), AllFunction.makeUint8(program, Constants.WSL_2)))), Constants.WSL_3);
            return null;
        });

        tests.put("equality", () -> {
            Program program = AllFunction.doPrep("bool foo(uint x, uint y) { return x == y; }");
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, Constants.WSL_7), AllFunction.makeUint(program, Constants.WSL_5)))), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, Constants.WSL_7), AllFunction.makeUint(program, Constants.WSL_7)))), true);

            program = AllFunction.doPrep("bool foo(uint8 x, uint8 y) { return x == y; }");
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, Constants.WSL_7), AllFunction.makeUint8(program, Constants.WSL_5)))), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, Constants.WSL_7), AllFunction.makeUint8(program, Constants.WSL_7)))), true);

            program = AllFunction.doPrep("bool foo(int x, int y) { return x == y; }");
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7), AllFunction.makeInt(program, Constants.WSL_5)))), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7), AllFunction.makeInt(program, Constants.WSL_7)))), true);

            program = AllFunction.doPrep("bool foo(bool x, bool y) { return x == y; }");
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, false), AllFunction.makeBool(program, true)))), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>( Arrays.asList(AllFunction.makeBool(program, true), AllFunction.makeBool(program, false)))), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, false), AllFunction.makeBool(program, false)))), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, true), AllFunction.makeBool(program, true)))), true);

            return null;
        });

        tests.put("logicalNegation", () -> {
            Program program = AllFunction.doPrep("bool foo(bool x) { return !x; }");
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, true)))), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, false)))), true);
            return null;
        });

        tests.put("notEquality", () -> {
            Program program = AllFunction.doPrep("bool foo(uint x, uint y) { return x != y; }");
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, Constants.WSL_7), AllFunction.makeUint(program, Constants.WSL_5)))), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, Constants.WSL_7), AllFunction.makeUint(program, Constants.WSL_7)))), false);

            program = AllFunction.doPrep("bool foo(uint8 x, uint8 y) { return x != y; }");
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, Constants.WSL_7), AllFunction.makeUint8(program, Constants.WSL_5)))), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, Constants.WSL_7), AllFunction.makeUint8(program, Constants.WSL_7)))), false);

            program = AllFunction.doPrep("bool foo(int x, int y) { return x != y; }");
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7), AllFunction.makeInt(program, Constants.WSL_5)))), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7), AllFunction.makeInt(program, Constants.WSL_7)))), false);

            program = AllFunction.doPrep("bool foo(bool x, bool y) { return x != y; }");
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, false), AllFunction.makeBool(program, true)))), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, true), AllFunction.makeBool(program, false)))), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, false), AllFunction.makeBool(program, false)))), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, true), AllFunction.makeBool(program, true)))), false);

            return null;
        });

        tests.put("equalityTypeFailure", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("bool foo(int x, uint y) { return x == y; }"),
                    (RuntimeException e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("/internal/test:1"));
            return null;
        });

        tests.put("generalNegation", () -> {
            Program program = AllFunction.doPrep("bool foo(int x) { return !x; }");
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7)))), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 0)))), true);
            return null;
        });

        tests.put("add1", () -> {
            Program program = AllFunction.doPrep("int foo(int x) { return x + 1; }");
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_42)))), Constants.WSL_43);
            return null;
        });

        tests.put("simpleGeneric", () -> {
            Program program = AllFunction.doPrep("""
                    T id<T>(T x) { return x; }
                    int foo(int x) { return id(x) + 1; }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_42)))), Constants.WSL_43);
            return null;
        });

        tests.put("nameResolutionFailure", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("int foo(int x) { return x + y; }"),
                    (RuntimeException e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("/internal/test:1"));
            return null;
        });

        tests.put("simpleVariable", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int p)
                    {
                        int result = p;
                        return result;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_42)))), Constants.WSL_42);
            return null;
        });

        tests.put("simpleAssignment", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int p)
                    {
                        int result;
                        result = p;
                        return result;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_42)))), Constants.WSL_42);
            return null;
        });

        tests.put("simpleDefault", () -> {
            Program program = AllFunction.doPrep("""
                    int foo()
                    {
                        int result;
                        return result;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), 0);
            return null;
        });

        tests.put("simpleDereference", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(device int* p)
                    {
                        return *p;
                    }
                      """);
            EBuffer buffer = new EBuffer(1);
            buffer.eBufferSet(0, Constants.WSL_13);
            AllFunction.checkInt(
                    program,
                    AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(TypedValue.box(new PtrType(Constants.externalOrigin, "device", program.intrinsics.int32), new EPtr(buffer, 0))))),
                    Constants.WSL_13);
            return null;
        });

        tests.put("dereferenceStore", () -> {
            Program program = AllFunction.doPrep("""
                    void foo(device int* p)
                    {
                          *p = 52;
                    }
                      """);
            EBuffer buffer = new EBuffer(1);
            buffer.eBufferSet(0, Constants.WSL_13);
            AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(TypedValue.box(new PtrType(Constants.externalOrigin, "device", program.intrinsics.int32), new EPtr(buffer, 0)))));
            Object result = buffer.eBufferGet(0);
            if ((result != null && (double) result != Constants.WSL_52)) {
                throw new Error("Expected buffer to contain 52 but it contains: " + result);
            }
            return null;
        });

        tests.put("simpleMakePtr", () -> {
            Program program = AllFunction.doPrep("""
                    thread int* foo()
                    {
                        int x = 42;
                        return &x;
                    }
                    """);
            TypedValue result = AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>());
            if (!result.hmType().isPtr()) {
                throw new Error("Return type is not a pointer: " + result.hmType());
            }
            if ((result.hmType() instanceof ArrayRefType || result.hmType() instanceof ReferenceType) && result.hmType().elementType().equals(program.intrinsics.int32) == null) {
                throw new Error("Return type is not a pointer to an int: " + result.hmType());
            }

            if (!(result.value() instanceof EPtr)) {
                throw new Error("Return value is not an EPtr: " + result.value());
            }
            int value = (int) ((double) ((EPtr) result.value()).loadValue());

            if (value != Constants.WSL_42) {
                throw new Error("Expected 42 but got: " + value);
            }
            return null;
        });

        tests.put("threadArrayLoad", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(thread int[] array)
                    {
                        return array[0u];
                    }
                      """);
            EBuffer buffer = new EBuffer(1);
            buffer.eBufferSet(0, Constants.WSL_89);

            TypedValue result = AllFunction.callFunction(
                    program,
                    "foo",
                    new ArrayList<>(),
                    new ArrayList<>(Arrays.asList(TypedValue.box(new ArrayRefType(Constants.externalOrigin, "thread", program.intrinsics.int32), new EArrayRef(new EPtr(buffer, 0), 1))))
            );
            AllFunction.checkInt(program, result, Constants.WSL_89);

            return null;
        });

        tests.put("threadArrayLoadIntLiteral", () -> {
            Program program = AllFunction.doPrep("""
                                        
                         int foo(thread int[] array)
                         {
                             return array[0];
                         }
                                        
                    """);
            EBuffer buffer = new EBuffer(1);
            buffer.eBufferSet(0, Constants.WSL_89);
            TypedValue result = AllFunction.callFunction(
                    program,
                    "foo",
                    new ArrayList<>(),
                    new ArrayList<>(Arrays.asList(TypedValue.box(new ArrayRefType(Constants.externalOrigin, "thread", program.intrinsics.int32), new EArrayRef(new EPtr(buffer, 0), 1))))
            );
            AllFunction.checkInt(program, result, Constants.WSL_89);

            return null;
        });

        tests.put("deviceArrayLoad", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(device int[] array)
                    {
                        return array[0u];
                    }
                    """);
            EBuffer buffer = new EBuffer(1);
            buffer.eBufferSet(0, Constants.WSL_89);

            TypedValue result = AllFunction.callFunction(
                    program,
                    "foo",
                    new ArrayList<>(),
                    new ArrayList<>(Arrays.asList(TypedValue.box(new ArrayRefType(Constants.externalOrigin, "device", program.intrinsics.int32), new EArrayRef(new EPtr(buffer, 0), 1)))));
            AllFunction.checkInt(program, result, Constants.WSL_89);
            return null;
        });

        tests.put("threadArrayStore", () -> {
            Program program = AllFunction.doPrep("""
                                        
                        void foo(thread int[] array, int value)
                        {
                            array[0u] = value;
                        }
                                        
                    """);
            EBuffer buffer = new EBuffer(1);
            buffer.eBufferSet(0, Constants.WSL_15);
            TypedValue arrayRef = TypedValue.box(new ArrayRefType(Constants.externalOrigin, "thread", program.intrinsics.int32), new EArrayRef(new EPtr(buffer, 0), 1));
            AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(arrayRef,AllFunction.makeInt(program, Constants.WSL_65))));
            if ((int) buffer.eBufferGet(0) != Constants.WSL_65) {
                throw new Error("Bad value stored into buffer (expected 65): " + buffer.eBufferGet(0));
            }
            AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(arrayRef,AllFunction.makeInt(program, Constants.WSLNEGATIVE_111))));
            if ((int) buffer.eBufferGet(0) != Constants.WSLNEGATIVE_111) {
                throw new Error("Bad value stored into buffer (expected -111): " + buffer.eBufferGet(0));
            }
            return null;
        });

        tests.put("deviceArrayStore", () -> {
            Program program = AllFunction.doPrep("""
                     void foo(device int[] array, int value)
                     {
                         array[0u] = value;
                     }
                    """);
            EBuffer buffer = new EBuffer(1);
            buffer.eBufferSet(0, Constants.WSL_15);
            TypedValue arrayRef =  TypedValue.box(new ArrayRefType(Constants.externalOrigin, "device", program.intrinsics.int32), new EArrayRef(new EPtr(buffer, 0), 1));
            AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(arrayRef,AllFunction.makeInt(program, Constants.WSL_65))));


            if ((int) buffer.eBufferGet(0) != Constants.WSL_65) {
                throw new Error("Bad value stored into buffer (expected 65): " + buffer.eBufferGet(0));
            }
            AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(arrayRef,AllFunction.makeInt(program, Constants.WSLNEGATIVE_111))));
            if ((int) buffer.eBufferGet(0) != Constants.WSLNEGATIVE_111) {
                throw new Error("Bad value stored into buffer (expected -111): " + buffer.eBufferGet(0));
            }

            return null;
        });

        tests.put("deviceArrayStoreIntLiteral", () -> {
            Program program = AllFunction.doPrep("""
                     void foo(device int[] array, int value)
                     {
                         array[0] = value;
                     }
                    """);
            EBuffer buffer = new EBuffer(1);
            buffer.eBufferSet(0, Constants.WSL_15);

            TypedValue arrayRef = TypedValue.box(new ArrayRefType(Constants.externalOrigin, "device", program.intrinsics.int32), new EArrayRef(new EPtr(buffer, 0), 1));
            AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(arrayRef,AllFunction.makeInt(program, Constants.WSL_65))));
            if ((int) buffer.eBufferGet(0) != Constants.WSL_65) {
                throw new Error("Bad value stored into buffer (expected 65): " + buffer.eBufferGet(0));
            }
            AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(arrayRef,AllFunction.makeInt(program, Constants.WSLNEGATIVE_111))));
            if ((int) buffer.eBufferGet(0) != Constants.WSLNEGATIVE_111) {
                throw new Error("Bad value stored into buffer (expected -111): " + buffer.eBufferGet(0));
            }

            return null;
        });

        tests.put("simpleProtocol", () -> {
            Program program = AllFunction.doPrep("""
                    protocol MyAddable {
                        MyAddable operator+(MyAddable, MyAddable);
                    }
                    T add<T:MyAddable>(T a, T b)
                    {
                        return a + b;
                    }
                    int foo(int x)
                    {
                        return add(x, 73);
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_45)))), Constants.WSL_45 + Constants.WSL_73);
            return null;
        });

        tests.put("typeMismatchReturn", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int foo()
                            {
                                return 0u;
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("typeMismatchVariableDecl", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                                        
                            void foo(uint x)
                            {
                                int y = x;
                            }
                                            
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("typeMismatchAssignment", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void foo(uint x)
                            {
                                int y;
                                y = x;
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("typeMismatchReturnParam", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                                        
                            int foo(uint x)
                            {
                                return x;
                            }
                                                        
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("badAdd", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    void bar<T>(T) { }
                                    void foo(int x, uint y)
                                    {
                                        bar(x + y);
                                    }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("native int32 operator+<>(int32,int32)")
            );
            return null;
        });

        tests.put("lexerKeyword", () -> {
            List<LexerToken> result = AllFunction.doLex("ident for while 123 123u { } {asd asd{ 1a3 1.2 + 3.4 + 1. + .2 1.2d 0.d .3d && ||");
            if (result.toArray().length != Constants.WSL_25) {
                throw new Error("Lexer emitted an incorrect number of tokens (expected 23): " + result.toArray().length);
            }
            AllFunction.checkLexerToken(result.get(0), 0, "identifier", "ident");
            AllFunction.checkLexerToken(result.get(1), Constants.WSL_6, "keyword", "for");
            AllFunction.checkLexerToken(result.get(Constants.WSL_2), Constants.WSL_10, "keyword", "while");
            AllFunction.checkLexerToken(result.get(Constants.WSL_3), Constants.WSL_16, "intLiteral", "123");
            AllFunction.checkLexerToken(result.get(Constants.WSL_4), Constants.WSL_20, "uintLiteral", "123u");
            AllFunction.checkLexerToken(result.get(Constants.WSL_5), Constants.WSL_25, "punctuation", "{");
            AllFunction.checkLexerToken(result.get(Constants.WSL_6), Constants.WSL_27, "punctuation", "}");
            AllFunction.checkLexerToken(result.get(Constants.WSL_7), Constants.WSL_29, "punctuation", "{");
            AllFunction.checkLexerToken(result.get(Constants.WSL_8), Constants.WSL_30, "identifier", "asd");
            AllFunction.checkLexerToken(result.get(Constants.WSL_9), Constants.WSL_34, "identifier", "asd");
            AllFunction.checkLexerToken(result.get(Constants.WSL_10), Constants.WSL_37, "punctuation", "{");
            AllFunction.checkLexerToken(result.get(Constants.WSL_11), Constants.WSL_39, "intLiteral", "1");
            AllFunction.checkLexerToken(result.get(Constants.WSL_12), Constants.WSL_40, "identifier", "a3");
            AllFunction.checkLexerToken(result.get(Constants.WSL_13), Constants.WSL_43, "floatLiteral", "1.2");
            AllFunction.checkLexerToken(result.get(Constants.WSL_14), Constants.WSL_47, "punctuation", "+");
            AllFunction.checkLexerToken(result.get(Constants.WSL_15), Constants.WSL_49, "floatLiteral", "3.4");
            AllFunction.checkLexerToken(result.get(Constants.WSL_16), Constants.WSL_53, "punctuation", "+");
            AllFunction.checkLexerToken(result.get(Constants.WSL_17), Constants.WSL_55, "floatLiteral", "1.");
            AllFunction.checkLexerToken(result.get(Constants.WSL_18), Constants.WSL_58, "punctuation", "+");
            AllFunction.checkLexerToken(result.get(Constants.WSL_19), Constants.WSL_60, "floatLiteral", ".2");
            AllFunction.checkLexerToken(result.get(Constants.WSL_20), Constants.WSL_63, "floatLiteral", "1.2d");
            AllFunction.checkLexerToken(result.get(Constants.WSL_21), Constants.WSL_68, "floatLiteral", "0.d");
            AllFunction.checkLexerToken(result.get(Constants.WSL_22), Constants.WSL_72, "floatLiteral", ".3d");
            AllFunction.checkLexerToken(result.get(Constants.WSL_23), Constants.WSL_76, "punctuation", "&&");
            AllFunction.checkLexerToken(result.get(Constants.WSL_24), Constants.WSL_79, "punctuation", "||");

            return null;
        });

        tests.put("simpleNoReturn", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("int foo() { }"),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("simpleUnreachableCode", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                             void foo()
                            {
                                return;
                                int x;
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("simpleStruct", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo {
                        int x;
                        int y;
                    }
                    Foo foo(Foo foo)
                    {
                        Foo result;
                        result.x = foo.y;
                        result.y = foo.x;
                        return result;
                    }
                    """);
            HmType structType = program.types().get("Foo");
            if (structType == null) {
                throw new Error("Did not find Foo type");
            }
            EBuffer buffer = new EBuffer(Constants.WSL_2);
            buffer.eBufferSet(0, Constants.WSL_62);
            buffer.eBufferSet(1, Constants.WSL_24);


            TypedValue result = AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(new TypedValue(structType, new EPtr(buffer, 0)))));

            if (result.hmType().equals(structType) == null) {
                throw new Error("Wrong result type: ${result.hmType}");
            }
            Object x = result.ePtr().ePtrGet(0);
            Object y = result.ePtr().ePtrGet(1);
            if ((int) x != Constants.WSL_24) {
                throw new Error("Wrong result for x: ${x} (y = ${y})");
            }
            if ((int) y != Constants.WSL_62) {
                throw new Error("Wrong result for y: ${y} (x + ${x})");
            }
            return null;
        });

        tests.put("genericStructInstance", () -> {
            Program program = AllFunction.doPrep("""
                     struct Foo<T> {
                        T x;
                        T y;
                    }
                    Foo<int> foo(Foo<int> foo)
                    {
                        Foo<int> result;
                        result.x = foo.y;
                        result.y = foo.x;
                        return result;
                    }
                    
                    """);
            TypeRef structType = TypeRef.instantiate((StructType) program.types().get("Foo"), new ArrayList<HmType>(List.of(program.intrinsics.int32)));
            EBuffer buffer = new EBuffer(Constants.WSL_2);
            buffer.eBufferSet(0, Constants.WSL_62);
            buffer.eBufferSet(1, Constants.WSL_24);
            TypedValue result = AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(new TypedValue(structType, new EPtr(buffer, 0)))));
            Object x = result.ePtr().ePtrGet(0);
            Object y = result.ePtr().ePtrGet(1);
            if ((int) x != Constants.WSL_24) {
                throw new Error("Wrong result for x: ${x} (y = ${y})");
            }
            if ((int) y != Constants.WSL_62) {
                throw new Error("Wrong result for y: ${y} (x + ${x})");
            }
            return null;
        });

        tests.put("doubleGenericCallsDoubleGeneric", () -> {
            AllFunction.doPrep("""
                    void foo<T, U>(T, U) { }
                    void bar<V, W>(V x, W y) { foo(x, y); }
                    """);
            return null;
        });

        tests.put("doubleGenericCallsSingleGeneric", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void foo<T>(T, T) { }
                            void bar<V, W>(V x, W y) { foo(x, y); }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("loadNull", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    void sink<T>(T) { }
                                    void foo() { sink(*null); }
                                    """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Type passed to dereference is not a pointer: null")
            );
            return null;
        });

        tests.put("storeNull", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    void foo() { *null = 42; }
                                    """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Type passed to dereference is not a pointer: null")
            );
            return null;
        });

        tests.put("returnNull", () -> {
            Program program = AllFunction.doPrep("""
                    thread int* foo() { return null; }
                    """);
            TypedValue result = AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>());
            if (!result.hmType().isPtr()) {
                throw new Error("Return type is not a pointer: " + result.hmType());
            }
            if ((result.hmType() instanceof ArrayRefType || result.hmType() instanceof ReferenceType) && result.hmType().elementType().equals(program.intrinsics.int32) == null) {
                throw new Error("Return type is not a pointer to an int: " + result.hmType());
            }

            if (result.value() != null) {
                throw new Error("Return value is not null: " + result.value());
            }
            return null;
        });

        tests.put("dereferenceDefaultNull", () -> {
            Program program = AllFunction.doPrep("""
                    int foo()
                    {
                        thread int* p;
                        return *p;
                    }
                    """);
            AllFunction.checkFail(
                    () -> AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()),
                    (RuntimeException e) -> e instanceof WTrapError);
            return null;
        });

        tests.put("defaultInitializedNull", () -> {
            Program program = AllFunction.doPrep("""
                    int foo()
                    {
                        thread int* p = null;;
                        return *p;
                    }
                    """);
            AllFunction.checkFail(
                    () -> AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()),
                    (RuntimeException e) -> e instanceof WTrapError);
            return null;
        });

        tests.put("passNullToPtrMonomorphic", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(thread int* ptr)
                    {
                        return *ptr;
                    }
                    int bar()
                    {
                        return foo(null);
                    }
                    """);
            AllFunction.checkFail(
                    () -> AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("passNullToPtrPolymorphic", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            T foo<T>(thread T* ptr)
                            {
                                return *ptr;
                            }
                            int bar()
                            {
                                return foo(null);
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("passNullToPolymorphic", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            T foo<T>(T ptr)
                            {
                                return ptr;
                            }
                            int bar()
                            {
                                return foo(null);
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("loadNullArrayRef", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void sink<T>(T) { }
                            void foo() { sink(null[0u]); }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Cannot resolve access"));
            return null;
        });

        tests.put("storeNullArrayRef", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void foo() { null[0u] = 42; }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Cannot resolve access"));
            return null;
        });

        tests.put("returnNullArrayRef", () -> {
            Program program = AllFunction.doPrep("""
                    thread int[] foo() { return null; }
                    """);
            TypedValue result = AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>());
            if (!result.hmType().isArrayRef()) {
                throw new Error("Return type is not an array reference: " + result.hmType());
            }
            if ((result.hmType() instanceof ArrayRefType || result.hmType() instanceof ReferenceType) && result.hmType().elementType().equals(program.intrinsics.int32) == null) {
                throw new Error("Return type is not an int array reference: " + result.hmType());
            }

            if (result.value() != null) {
                throw new Error("Return value is not null: " + result.value());
            }
            return null;
        });

        tests.put("dereferenceDefaultNullArrayRef", () -> {
            Program program = AllFunction.doPrep("""
        
            int foo()
            {
                thread int[] p;
                return p[0u];
            }
        
        """);
            AllFunction.checkFail(
                    () -> AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()),
                    (RuntimeException e) -> e instanceof WTrapError);
            return null;
        });

        tests.put("defaultInitializedNullArrayRef", () -> {
            Program program = AllFunction.doPrep("""
                    int foo()
                    {
                        thread int[] p = null;
                        return p[0u];
                    }
                    """);
            AllFunction.checkFail(
                    () -> AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()),
                    (RuntimeException e) -> e instanceof WTrapError);
            return null;
        });

        tests.put("defaultInitializedNullArrayRefIntLiteral", () -> {
            Program program = AllFunction.doPrep("""
                    int foo()
                    {
                        thread int[] p = null;
                        return p[0];
                    }
                    """);
            AllFunction.checkFail(
                    () -> AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()),
                    (RuntimeException e) -> e instanceof WTrapError);
            return null;
        });

        tests.put("passNullToPtrMonomorphicArrayRef", () -> {
            Program program = AllFunction.doPrep("""
        
            int foo(thread int[] ptr)
            {
                return ptr[0u];
            }
            int bar()
            {
                return foo(null);
            }
        
            """);
            AllFunction.checkFail(
                    () -> AllFunction.callFunction(program, "bar", new ArrayList<>(), new ArrayList<>()),
                    (e -> e instanceof WTrapError));

            return null;
        });

        tests.put("passNullToPtrPolymorphicArrayRef", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            T foo<T>(thread T[] ptr)
                            {
                                return ptr[0u];
                            }
                            int bar()
                            {
                                return foo(null);
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("returnIntLiteralUint", () -> {
            Program program = AllFunction.doPrep("uint foo() { return 42; }");
            AllFunction.checkNumber(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_42);
            return null;
        });

        tests.put("returnIntLiteralDouble", () -> {
            Program program = AllFunction.doPrep("double foo() { return 42; }");
            AllFunction.checkNumber(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_42);
            return null;
        });

        tests.put("badIntLiteralForInt", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("void foo() { int x = 3000000000; }"),
                    (RuntimeException e) -> e instanceof WSyntaxError);
            return null;
        });

        tests.put("badIntLiteralForUint", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("void foo() { uint x = 5000000000; }"),
                    (RuntimeException e) -> e instanceof WSyntaxError);
            return null;
        });

        tests.put("badIntLiteralForDouble", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("void foo() { double x = 5000000000000000000000000000000000000; }"),
                    (RuntimeException e) -> e instanceof WSyntaxError);
            return null;
        });

        tests.put("passNullAndNotNull", () -> {
            Program program = AllFunction.doPrep("""
                    T bar<T>(device T* p, device T*)
                    {
                        return *p;
                    }
                    int foo(device int* p)
                    {
                        return bar(p, null);
                    }
                    """);
            EBuffer buffer = new EBuffer(1);
            buffer.eBufferSet(0, Constants.WSL_13);
            AllFunction.checkInt(
                    program,
                    AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(TypedValue.box(new PtrType(Constants.externalOrigin, "device", program.intrinsics.int32), new EPtr(buffer, 0))))),
                    Constants.WSL_13
            );

            return null;
        });

        tests.put("passNullAndNotNullFullPoly", () -> {
            Program program = AllFunction.doPrep("""
                    T bar<T>(T p, T)
                    {
                        return p;
                    }
                    int foo(device int* p)
                    {
                        return *bar(p, null);
                    }
                    """);
            EBuffer buffer = new EBuffer(1);
            buffer.eBufferSet(0, Constants.WSL_13);

            AllFunction.checkInt(
                    program,
                    AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(TypedValue.box(new PtrType(Constants.externalOrigin, "device", program.intrinsics.int32), new EPtr(buffer, 0))))),
                    Constants.WSL_13
            );

            return null;
        });

        tests.put("passNullAndNotNullFullPolyReverse", () -> {
            Program program = AllFunction.doPrep("""
                    T bar<T>(T, T p)
                    {
                        return p;
                    }
                    int foo(device int* p)
                    {
                        return *bar(null, p);
                    }
                    """);
            EBuffer buffer = new EBuffer(1);
            buffer.eBufferSet(0, Constants.WSL_13);
            AllFunction.checkInt(
                    program,
                    AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(TypedValue.box(new PtrType(Constants.externalOrigin, "device", program.intrinsics.int32), new EPtr(buffer, 0))))),
                    Constants.WSL_13);
            return null;
        });

        tests.put("nullTypeVariableUnify", () -> {
            NullType left = new NullType(Constants.externalOrigin);
            TypeVariable right = new TypeVariable(Constants.externalOrigin, "T", null);
            if (left.equals(right) != null) {
                throw new Error("Should not be equal but are: " + left + " and " + right);
            }
            if (right.equals(left) != null) {
                throw new Error("Should not be equal but are: " + left + " and " + right);
            }
            BiFunction<ArrayList<ArrayList<String>>, Function<List<List<String>>, Object>, Object> everyOrder = new BiFunction<ArrayList<ArrayList<String>>, Function<List<List<String>>, Object>, Object>() {
                @Override
                public Object apply(ArrayList<ArrayList<String>> arrayLists, Function<List<List<String>>, Object> listObjectFunction) {

                    NullTypeVariableUnifyInterface recurse = new NullTypeVariableUnifyInterface() {
                        @Override
                        public Object apply(ArrayList<ArrayList<String>> array, Function<List<List<String>>, Object> callback, List<List<String>> order) {
                            if (array.size() == 0) {
                                return callback.apply(order);
                            }
                            for (int i = 0; i < array.size(); ++i) {
                                ArrayList<ArrayList<String>> nextArray = new ArrayList<>();
                                nextArray.addAll(array);
                                nextArray.remove(i);
                                return this.apply(nextArray, callback, order);
                            }
                            return null;
                        }
                    };
                    return recurse.apply(arrayLists, listObjectFunction, new ArrayList<>());
                }
            };

            Function<List<String>, ArrayList<ArrayList<String>>> everyPair = (things) -> {
                ArrayList<ArrayList<String>> result = new ArrayList<ArrayList<String>>();
                for (int i = 0; i < things.size(); ++i) {
                    for (int j = 0; j < things.size(); ++j) {
                        if (i != j) {
                            result.add(new ArrayList<>(Arrays.asList(things.get(i),things.get(j))));
                        }
                    }
                }
                return result;
            };
            ArrayList<ArrayList<String>> tmp = everyPair.apply(List.of("nullType", "variableType", "ptrType"));
            everyOrder.apply(tmp, (order) -> {
                Map<String, Object> types = new HashMap<>();
                types.put("nullType", new NullType(Constants.externalOrigin));
                types.put("variableType", new TypeVariable(Constants.externalOrigin, "T", null));
                types.put("ptrType", new PtrType(Constants.externalOrigin, "constant", new NativeType(Constants.externalOrigin, "foo_t", new ArrayList<>())));
                UnificationContext unificationContext = new UnificationContext(List.of((Node) types.get("variableType")));
                for (List<String> orderArr : order) {
                    HmType left1 = (HmType) types.get(orderArr.get(0));
                    HmType right1 = (HmType) types.get(orderArr.get(1));
                    Boolean result = left1.unify(unificationContext, right1);
                    if (!result) {
                        throw new Error("In order " + order + " cannot unify " + left1 + " with " + right1);
                    }
                }
                if (!unificationContext.verify().result) {
                    throw new Error("In order " + order.stream().map((value) -> "(" + value + ")").collect(Collectors.joining("")) + " cannot verify");
                }
                return null;
            });
            return null;
        });

        tests.put("doubleNot", () -> {
            Program program = AllFunction.doPrep("""
                    bool foo(bool x)
                    {
                        return !!x;
                    }
                    """);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, true)))), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, false)))), false);
            return null;
        });

        tests.put("simpleRecursion", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void foo<T>(T x)
                            {
                                foo(&x);
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("protocolMonoSigPolyDef", () -> {
            Program program = AllFunction.doPrep("""
                    struct IntAnd<T> {
                        int first;
                        T second;
                    }
                    IntAnd<T> intAnd<T>(int first, T second)
                    {
                        IntAnd<T> result;
                        result.first = first;
                        result.second = second;
                        return result;
                    }
                    protocol IntAndable {
                        IntAnd<int> intAnd(IntAndable, int);
                    }
                    int foo<T:IntAndable>(T first, int second)
                    {
                        IntAnd<int> result = intAnd(first, second);
                        return result.first + result.second;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_54), AllFunction.makeInt(program, Constants.WSL_12)))), Constants.WSL_54 + Constants.WSL_12);
            return null;
        });

        tests.put("protocolPolySigPolyDef", () -> {
            Program program = AllFunction.doPrep("""
                    struct IntAnd<T> {
                        int first;
                        T second;
                    }
                    IntAnd<T> intAnd<T>(int first, T second)
                    {
                        IntAnd<T> result;
                        result.first = first;
                        result.second = second;
                        return result;
                    }
                    protocol IntAndable {
                        IntAnd<T> intAnd<T>(IntAndable, T);
                    }
                    int foo<T:IntAndable>(T first, int second)
                    {
                        IntAnd<int> result = intAnd(first, second);
                        return result.first + result.second;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_54), AllFunction.makeInt(program, Constants.WSL_12)))), Constants.WSL_54 + Constants.WSL_12);
            return null;
        });

        tests.put("protocolDoublePolySigDoublePolyDef", () -> {
            Program program = AllFunction.doPrep("""
                    struct IntAnd<T, U> {
                        int first;
                        T second;
                        U third;
                    }
                    IntAnd<T, U> intAnd<T, U>(int first, T second, U third)
                    {
                        IntAnd<T, U> result;
                        result.first = first;
                        result.second = second;
                        result.third = third;
                        return result;
                    }
                    protocol IntAndable {
                        IntAnd<T, U> intAnd<T, U>(IntAndable, T, U);
                    }
                    int foo<T:IntAndable>(T first, int second, int third)
                    {
                        IntAnd<int, int> result = intAnd(first, second, third);
                        return result.first + result.second + result.third;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_54), AllFunction.makeInt(program, Constants.WSL_12), AllFunction.makeInt(program, Constants.WSL_39)))), Constants.WSL_54 + Constants.WSL_12 + Constants.WSL_39);
            return null;
        });

        tests.put("protocolDoublePolySigDoublePolyDefExplicit", () -> {
            Program program = AllFunction.doPrep("""
                    struct IntAnd<T, U> {
                        int first;
                        T second;
                        U third;
                    }
                    IntAnd<T, U> intAnd<T, U>(int first, T second, U third)
                    {
                        IntAnd<T, U> result;
                        result.first = first;
                        result.second = second;
                        result.third = third;
                        return result;
                    }
                    protocol IntAndable {
                        IntAnd<T, U> intAnd<T, U>(IntAndable, T, U);
                    }
                    int foo<T:IntAndable>(T first, int second, int third)
                    {
                        IntAnd<int, int> result = intAnd<int, int>(first, second, third);
                        return result.first + result.second + result.third;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_54), AllFunction.makeInt(program, Constants.WSL_12), AllFunction.makeInt(program, Constants.WSL_39)))), Constants.WSL_54 + Constants.WSL_12 + Constants.WSL_39);
            return null;
        });

        tests.put("variableShadowing", () -> {
            Program program = AllFunction.doPrep("""
                    int foo()
                    {
                        int y;
                        int x = 7;
                        {
                            int x = 8;
                            y = x;
                        }
                        return y;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_8);
            program = AllFunction.doPrep("""
                    int foo()
                    {
                        int y;
                        int x = 7;
                        {
                            int x = 8;
                        }
                        y = x;
                        return y;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_7);
            return null;
        });

        tests.put("ifStatement", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        int y = 6;
                        if (x == 7) {
                            y = 8;
                        }
                        return y;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_6)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSL_8);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_8)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_9)))), Constants.WSL_6);
            return null;
        });

        tests.put("ifElseStatement", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        int y = 6;
                        if (x == 7) {
                            y = 8;
                        } else {
                            y = 9;
                        }
                        return y;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_9);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))), Constants.WSL_9);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_9);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_6)))), Constants.WSL_9);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSL_8);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_8)))), Constants.WSL_9);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_9)))), Constants.WSL_9);
            return null;
        });

        tests.put("ifElseIfStatement", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        int y = 6;
                        if (x == 7) {
                            y = 8;
                        } else if (x == 8) {
                            y = 9;
                        }
                        return y;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_6)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSL_8);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_8)))), Constants.WSL_9);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_9)))), Constants.WSL_6);
            return null;
        });

        tests.put("ifElseIfElseStatement", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        int y = 6;
                        if (x == 7) {
                            y = 8;
                        } else if (x == 8) {
                            y = 9;
                        } else {
                            y = 10;
                        }
                        return y;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_10);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))), Constants.WSL_10);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_10);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_6)))), Constants.WSL_10);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSL_8);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_8)))), Constants.WSL_9);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_9)))), Constants.WSL_10);
            return null;
        });

        tests.put("returnIf", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                        
                                int foo(int x)
                                {
                                    int y = 6;
                                    if (x == 7) {
                                        return y;
                                    }
                                }
                                        
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                        
                                int foo(int x)
                                {
                                    int y = 6;
                                    if (x == 7) {
                                        return y;
                                    } else {
                                        y = 8;
                                    }
                                }
                                        
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                        
                                int foo(int x)
                                {
                                    int y = 6;
                                    if (x == 7) {
                                        y = 8;
                                    } else {
                                        return y;
                                    }
                                }
                                        
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            Program program = AllFunction.doPrep("""
                            
                        int foo(int x)
                        {
                            int y = 6;
                            if (x == 7) {
                                return 8;
                            } else {
                                return 10;
                            }
                        }
                            
                    """);

            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_10);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))), Constants.WSL_10);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_10);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_6)))), Constants.WSL_10);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSL_8);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_8)))), Constants.WSL_10);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_9)))), Constants.WSL_10);

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                        
                                int foo(int x)
                                {
                                    int y = 6;
                                    if (x == 7) {
                                        return 8;
                                    } else if (x == 9) {
                                        return 10;
                                    }
                                }
                                        
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            program = AllFunction.doPrep("""
                            
                        int foo(int x)
                        {
                            int y = 6;
                            if (x == 7) {
                                return 8;
                            } else {
                                y = 9;
                            }
                            return y;
                        }
                            
                    """);

            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_9);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))), Constants.WSL_9);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_9);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_6)))), Constants.WSL_9);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSL_8);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_8)))), Constants.WSL_9);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_9)))), Constants.WSL_9);

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                        
                                int foo(int x)
                                {
                                    int y = 6;
                                    if (x == 7) {
                                        return 8;
                                    } else {
                                        return 10;
                                    }
                                    return 11;
                                }
                                        
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);

            program = AllFunction.doPrep("""
                            
                        int foo(int x)
                        {
                            int y = 6;
                            if (x == 7)
                                int y = 8;
                            return y;
                        }
                            
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSL_6);
            return null;
        });

        tests.put("simpleWhile", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        while (x < 13)
                            x = x * 2;
                        return x;
                    }
                    """);
            TypedValue[] thirdList = {AllFunction.makeInt(program, 1)};
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(thirdList))), Constants.WSL_16);

            return null;
        });

        tests.put("protocolMonoPolySigDoublePolyDefExplicit", () -> {
            AllFunction.checkFail(
                    () -> {
                        Program program = AllFunction.doPrep("""
                                struct IntAnd<T, U> {
                                    int first;
                                    T second;
                                    U third;
                                }
                                IntAnd<T, U> intAnd<T, U>(int first, T second, U third)
                                {
                                    IntAnd<T, U> result;
                                    result.first = first;
                                    result.second = second;
                                    result.third = third;
                                    return result;
                                }
                                protocol IntAndable {
                                    IntAnd<T, int> intAnd<T>(IntAndable, T, int);
                                }
                                int foo<T:IntAndable>(T first, int second, int third)
                                {
                                    IntAnd<int, int> result = intAnd<int>(first, second, third);
                                    return result.first + result.second + result.third;
                                }
                                """);
                        return AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_54), AllFunction.makeInt(program, Constants.WSL_12), AllFunction.makeInt(program, Constants.WSL_39))));
                    },
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("ambiguousOverloadSimple", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void foo<T>(int, T) { }
                            void foo<T>(T, int) { }
                            void bar(int a, int b) { foo(a, b); }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("ambiguousOverloadOverlapping", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void foo<T>(int, T) { }
                            void foo<T>(T, T) { }
                            void bar(int a, int b) { foo(a, b); }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("ambiguousOverloadTieBreak", () -> {
            AllFunction.doPrep("""
                    void foo<T>(int, T) { }
                    void foo<T>(T, T) { }
                    void foo(int, int) { }
                    void bar(int a, int b) { foo(a, b); }
                    """);
            return null;
        });

        tests.put("intOverloadResolution", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int) { return 1; }
                    int foo(uint) { return 2; }
                    int foo(double) { return 3; }
                    int bar() { return foo(42); }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "bar", new ArrayList<>(), new ArrayList<>()), 1);
            return null;
        });

        tests.put("intOverloadResolutionReverseOrder", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(double) { return 3; }
                    int foo(uint) { return 2; }
                    int foo(int) { return 1; }
                    int bar() { return foo(42); }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "bar", new ArrayList<>(), new ArrayList<>()), 1);
            return null;
        });

        tests.put("intOverloadResolutionGeneric", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int) { return 1; }
                    int foo<T>(T) { return 2; }
                    int bar() { return foo(42); }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "bar", new ArrayList<>(), new ArrayList<>()), 1);
            return null;
        });

        tests.put("intLiteralGeneric", () -> {
            Program program = AllFunction.doPrep("""
                    int foo<T>(T x) { return 3478; }
                    int bar() { return foo(42); }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "bar", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3478);
            return null;
        });

        tests.put("intLiteralGenericWithProtocols", () -> {
            Program program = AllFunction.doPrep("""
                    protocol MyConvertibleToInt {
                        operator int(MyConvertibleToInt);
                    }
                    int foo<T:MyConvertibleToInt>(T x) { return int(x); }
                    int bar() { return foo(42); }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "bar", new ArrayList<>(), new ArrayList<>()), Constants.WSL_42);
            return null;
        });

        tests.put("uintLiteralGeneric", () -> {
            Program program = AllFunction.doPrep("""
                    int foo<T>(T x) { return 3478; }
                    int bar() { return foo(42u); }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "bar", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3478);
            return null;
        });

        tests.put("uintLiteralGenericWithProtocols", () -> {
            Program program = AllFunction.doPrep("""
                    protocol MyConvertibleToUint {
                        operator uint(MyConvertibleToUint);
                    }
                    uint foo<T:MyConvertibleToUint>(T x) { return uint(x); }
                    uint bar() { return foo(42u); }
                    """);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "bar", new ArrayList<>(), new ArrayList<>()), Constants.WSL_42);
            return null;
        });

        tests.put("intLiteralGenericSpecific", () -> {
            Program program = AllFunction.doPrep("""
                    T foo<T>(T x) { return x; }
                    int bar() { return foo(int(42)); }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "bar", new ArrayList<>(), new ArrayList<>()), Constants.WSL_42);
            return null;
        });

        tests.put("simpleConstexpr", () -> {
            Program program = AllFunction.doPrep("""
                    int foo<int a>(int b)
                    {
                        return a + b;
                    }
                    int bar(int b)
                    {
                        return foo<42>(b);
                    }
                    """);
            ArrayList<TypedValue> list = new ArrayList<>();
            list.add(AllFunction.makeInt(program, Constants.WSL_58));
            AllFunction.checkInt(program, AllFunction.callFunction(program, "bar", new ArrayList<>(), list), Constants.WSL_58 + Constants.WSL_42);
            return null;
        });

        tests.put("break", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        while (true) {
                            x = x * 2;
                            if (x >= 7)
                                break;
                        }
                        return x;
                    }
                    """);

            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 1)))), Constants.WSL_8);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_10)))), Constants.WSL_20);

            program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        while (true) {
                            while (true) {
                                x = x * 2;
                                if (x >= 7)
                                    break;
                            }
                            x = x - 1;
                            break;
                        }
                        return x;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 1)))), Constants.WSL_7);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_10)))), Constants.WSL_19);

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""

                int foo(int x)
                {
                    while (true) {
                        {
                            break;
                        }
                        x = x + 1;
                    }
                    return x;
                }

            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int foo(int x)
                            {
                                break;
                                return x;
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        while (true) {
                            if (x == 7) {
                                break;
                            }
                            x = x + 1;
                        }
                        return x;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 1)))), Constants.WSL_7);
            program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        while (true) {
                            break;
                        }
                        return x;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 1)))), 1);
            program = AllFunction.doPrep("""
                    int foo()
                    {
                        while (true) {
                            return 7;
                        }
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_7);
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int foo(int x)
                            {
                                while(true) {
                                    break;
                                    return 7;
                                }
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);

            return null;
        });

        tests.put("continue", () -> {
            Program program = AllFunction.doPrep("""
        
            int foo(int x)
            {
                while (x < 10) {
                    if (x == 8) {
                        x = x + 1;
                        continue;
                    }
                    x = x * 2;
                }
                return x;
            }
        
        """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 1)))), Constants.WSL_18);

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""

                int foo(int x)
                {
                    continue;
                    return x;

                }

            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("doWhile", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        int y = 7;
                        do {
                            y = 8;
                            break;
                        } while (x < 10);
                        return y;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 1)))), Constants.WSL_8);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_11)))), Constants.WSL_8);
            program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        int y = 7;
                        do {
                            y = 8;
                            break;
                        } while (y == 7);
                        return y;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 1)))), Constants.WSL_8);
            program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        int sum = 0;
                        do {
                            if (x == 11) {
                                x = 15;
                                continue;
                            }
                            sum = sum + x;
                            x = x + 1;
                        } while (x < 13);
                        return sum;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_9)))), Constants.WSL_19);
            return null;
        });

        tests.put("forLoop", () -> {
            Program program = AllFunction.doPrep("""
                      int foo(int x)
                      {
                          int sum = 0;
                          int i;
                          for (i = 0; i < x; i = i + 1) {
                              sum = sum + i;
                          }
                          return sum;
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_10);

            program = AllFunction.doPrep("""
                      int foo(int x)
                      {
                          int sum = 0;
                          for (int i = 0; i < x; i = i + 1) {
                              sum = sum + i;
                          }
                          return sum;
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))),Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList( AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_10);
            program = AllFunction.doPrep("""
                      int foo(int x)
                      {
                          int sum = 0;
                          int i = 100;
                          for (int i = 0; i < x; i = i + 1) {
                              sum = sum + i;
                          }
                          return sum;
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_10);
            program = AllFunction.doPrep("""
                      int foo(int x)
                      {
                          int sum = 0;
                          for (int i = 0; i < x; i = i + 1) {
                              if (i == 4)
                                  continue;
                              sum = sum + i;
                          }
                          return sum;
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_6)))), Constants.WSL_11);
            program = AllFunction.doPrep("""
                      int foo(int x)
                      {
                          int sum = 0;
                          for (int i = 0; i < x; i = i + 1) {
                              if (i == 5)
                                  break;
                              sum = sum + i;
                          }
                          return sum;
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_10);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_6)))), Constants.WSL_10);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSL_10);
            program = AllFunction.doPrep("""
                      int foo(int x)
                      {
                          int sum = 0;
                          for (int i = 0; ; i = i + 1) {
                              if (i >= x)
                                  break;
                              sum = sum + i;
                          }
                          return sum;
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_10);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_6)))), Constants.WSL_15);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSL_21);
            program = AllFunction.doPrep("""
                      int foo(int x)
                      {
                          int sum = 0;
                          int i = 0;
                          for ( ; ; i = i + 1) {
                              if (i >= x)
                                  break;
                              sum = sum + i;
                          }
                          return sum;
                      }
                    """);

            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_10);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_6)))), Constants.WSL_15);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSL_21);
            program = AllFunction.doPrep("""
                      int foo(int x)
                      {
                          int sum = 0;
                          int i = 0;
                          for ( ; ; ) {
                              if (i >= x)
                                  break;
                              sum = sum + i;
                              i = i + 1;
                          }
                          return sum;
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_10);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_6)))), Constants.WSL_15);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSL_21);
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    void foo(int x)
                                    {
                                        for (int i = 0; ; i = i + 1) {
                                            break;
                                            x = i;
                                        }
                                    }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            program = AllFunction.doPrep("""
                      int foo(int x)
                      {
                          for ( ; ; ) {
                              return 7;
                          }
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_7);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))), Constants.WSL_7);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_7);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_6)))), Constants.WSL_7);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSL_7);
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int foo(int x)
                                    {
                                        for ( ; x < 10; ) {
                                            return 7;
                                        }
                                    }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            program = AllFunction.doPrep("""
                      int foo(int x)
                      {
                          for ( ; true; ) {
                              return 7;
                          }
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))), Constants.WSL_7);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4)))), Constants.WSL_7);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_5)))), Constants.WSL_7);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_6)))), Constants.WSL_7);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSL_7);
            return null;
        });

        tests.put("chainConstexpr", () -> {
            Program program = AllFunction.doPrep("""
                    int foo<int a>(int b)
                    {
                        return a + b;
                    }
                    int bar<int a>(int b)
                    {
                        return foo<a>(b);
                    }
                    int baz(int b)
                    {
                        return bar<42>(b);
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "baz", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_58)))), Constants.WSL_58 + Constants.WSL_42);
            return null;
        });

        tests.put("chainGeneric", () -> {
            Program program = AllFunction.doPrep("""
                    T foo<T>(T x)
                    {
                        return x;
                    }
                    T bar<T>(thread T* ptr)
                    {
                        return *foo(ptr);
                    }
                    int baz(int x)
                    {
                        return bar(&x);
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "baz", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_37)))), Constants.WSL_37);
            return null;
        });

        tests.put("chainStruct", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo<T> {
                        T f;
                    }
                    struct Bar<T> {
                        Foo<thread T*> f;
                    }
                    int foo(thread Bar<int>* x)
                    {
                        return *x->f.f;
                    }
                    int bar(int a)
                    {
                        Bar<int> x;
                        x.f.f = &a;
                        return foo(&x);
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "bar", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_4657)))), Constants.WSL_4657);
            return null;
        });

        tests.put("chainStructNewlyValid", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo<T> {
                        T f;
                    }
                    struct Bar<T> {
                        Foo<device T*> f;
                    }
                    int foo(thread Bar<int>* x)
                    {
                        return *x->f.f;
                    }
                    int bar(device int* a)
                    {
                        Bar<int> x;
                        x.f.f = a;
                        return foo(&x);
                    }
                    """);
            EBuffer buffer = new EBuffer(1);
            buffer.eBufferSet(0, Constants.WSL_78453);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "bar", new ArrayList<>(), new ArrayList<>(Arrays.asList(TypedValue.box(new PtrType(Constants.externalOrigin, "device", program.intrinsics.int32), new EPtr(buffer, 0))))), Constants.WSL_78453);
            return null;
        });

        tests.put("chainStructDevice", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo<T> {
                        T f;
                    }
                    struct Bar<T> {
                        Foo<device T*> f;
                    }
                    int foo(thread Bar<int>* x)
                    {
                        return *x->f.f;
                    }
                    int bar(device int* a)
                    {
                        Bar<int> x;
                        x.f.f = a;
                        return foo(&x);
                    }
                    """);
            EBuffer buffer = new EBuffer(1);
            buffer.eBufferSet(0, Constants.WSL_79201);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "bar", new ArrayList<>(), new ArrayList<>(Arrays.asList(TypedValue.box(new PtrType(Constants.externalOrigin, "device", program.intrinsics.int32), new EPtr(buffer, 0))))), Constants.WSL_79201);
            return null;
        });

        tests.put("paramChainStructDevice", () -> {
            Program program = AllFunction.doPrep("""
        
            struct Foo<T> {
                T f;
            }
            struct Bar<T> {
                Foo<T> f;
            }
            int foo(thread Bar<device int*>* x)
            {
                return *x->f.f;
            }
            int bar(device int* a)
            {
                Bar<device int*> x;
                x.f.f = a;
                return foo(&x);
            }
        
        """);
            EBuffer buffer = new EBuffer(1);
            buffer.eBufferSet(0, Constants.WSL_79201);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "bar", new ArrayList<>(), new ArrayList<>(Arrays.asList(TypedValue.box(new PtrType(Constants.externalOrigin, "device", program.intrinsics.int32), new EPtr(buffer, 0))))), Constants.WSL_79201);
            return null;
        });

        tests.put("simpleProtocolExtends", () -> {
            Program program = AllFunction.doPrep("""
                    protocol Foo {
                        void foo(thread Foo*);
                    }
                    protocol Bar : Foo {
                        void bar(thread Bar*);
                    }
                    void fuzz<T:Foo>(thread T* p)
                    {
                        foo(p);
                    }
                    void buzz<T:Bar>(thread T* p)
                    {
                        fuzz(p);
                        bar(p);
                    }
                    void foo(thread int* p)
                    {
                        *p = *p + 743;
                    }
                    void bar(thread int* p)
                    {
                        *p = *p + 91;
                    }
                    int thingy(int a)
                    {
                        buzz(&a);
                        return a;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "thingy", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_642)))), Constants.WSL_642 + Constants.WSL_743 + Constants.WSL_91);
            return null;
        });

        tests.put("protocolExtendsTwo", () -> {
            Program program = AllFunction.doPrep("""
                    protocol Foo {
                        void foo(thread Foo*);
                    }
                    protocol Bar {
                        void bar(thread Bar*);
                    }
                    protocol Baz : Foo, Bar {
                        void baz(thread Baz*);
                    }
                    void fuzz<T:Foo>(thread T* p)
                    {
                        foo(p);
                    }
                    void buzz<T:Bar>(thread T* p)
                    {
                        bar(p);
                    }
                    void xuzz<T:Baz>(thread T* p)
                    {
                        fuzz(p);
                        buzz(p);
                        baz(p);
                    }
                    void foo(thread int* p)
                    {
                        *p = *p + 743;
                    }
                    void bar(thread int* p)
                    {
                        *p = *p + 91;
                    }
                    void baz(thread int* p)
                    {
                        *p = *p + 39;
                    }
                    int thingy(int a)
                    {
                        xuzz(&a);
                        return a;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "thingy", new ArrayList<>(),new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_642)))), Constants.WSL_642 + Constants.WSL_743 + Constants.WSL_91 + Constants.WSL_39);
            return null;
        });

        tests.put("prefixPlusPlus", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        ++x;
                        return x;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_64)))), Constants.WSL_65);
            return null;
        });

        tests.put("prefixPlusPlusResult", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        return ++x;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_64)))), Constants.WSL_65);
            return null;
        });

        tests.put("postfixPlusPlus", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        ++x;
                        return x;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_64)))), Constants.WSL_65);
            return null;
        });

        tests.put("postfixPlusPlusResult", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        return x++;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_64)))), Constants.WSL_64);
            return null;
        });

        tests.put("prefixMinusMinus", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        --x;
                        return x;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_64)))), Constants.WSL_63);
            return null;
        });

        tests.put("prefixMinusMinusResult", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        return --x;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_64)))), Constants.WSL_63);
            return null;
        });

        tests.put("postfixMinusMinus", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        x--;
                        return x;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_64)))), Constants.WSL_63);
            return null;
        });

        tests.put("postfixMinusMinusResult", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        return x--;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_64)))), Constants.WSL_64);
            return null;
        });

        tests.put("plusEquals", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        x += 42;
                        return x;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_385)))), Constants.WSL_385 + Constants.WSL_42);
            return null;
        });

        tests.put("plusEqualsResult", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        return x += 42;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_385)))), Constants.WSL_385 + Constants.WSL_42);
            return null;
        });

        tests.put("minusEquals", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        x -= 42;
                        return x;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_385)))), Constants.WSL_385 - Constants.WSL_42);
            return null;
        });

        tests.put("minusEqualsResult", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        return x -= 42;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_385)))), Constants.WSL_385 - Constants.WSL_42);
            return null;
        });

        tests.put("timesEquals", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        x *= 42;
                        return x;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_385)))), Constants.WSL_385 * Constants.WSL_42);
            return null;
        });

        tests.put("timesEqualsResult", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        return x *= 42;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_385)))), Constants.WSL_385 * Constants.WSL_42);
            return null;
        });

        tests.put("divideEquals", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        x /= 42;
                        return x;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_385)))), Constants.WSL_385 / Constants.WSL_42);
            return null;
        });

        tests.put("divideEqualsResult", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int x)
                    {
                        return x /= 42;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_385)))), Constants.WSL_385 / Constants.WSL_42);
            return null;
        });

        tests.put("twoIntLiterals", () -> {
            Program program = AllFunction.doPrep("""
                    bool foo()
                    {
                        return 42 == 42;
                    }
                    """);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), true);
            return null;
        });

        tests.put("unifyDifferentLiterals", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void bar<T>(T, T)
                            {
                            }
                            void foo()
                            {
                                bar(42, 42u);
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("unifyDifferentLiteralsBackwards", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void bar<T>(T, T)
                            {
                            }
                            void foo()
                            {
                                bar(42u, 42);
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("unifyVeryDifferentLiterals", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void bar<T>(T, T)
                            {
                            }
                            void foo()
                            {
                                bar(42, null);
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("unifyVeryDifferentLiteralsBackwards", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void bar<T>(T, T)
                            {
                            }
                            void foo()
                            {
                                bar(null, 42);
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("assignUintToInt", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void foo()
                            {
                                int x = 42u;
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Type mismatch in variable initialization"));
            return null;
        });

        tests.put("buildArrayThenSumIt", () -> {
            Program program = AllFunction.doPrep("""
                    int foo()
                    {
                        int[42] array;
                        for (uint i = 0; i < 42; i = i + 1)
                            array[i] = int(i + 5);
                        int result;
                        for (uint i = 0; i < 42; i = i + 1)
                            result = result + array[i];
                        return result;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_42 * Constants.WSL_5 + (Constants.WSL_42 * Constants.WSL_41) / Constants.WSL_2);
            return null;
        });

        tests.put("buildArrayThenSumItUsingArrayReference", () -> {
            Program program = AllFunction.doPrep("""
                    int bar(thread int[] array)
                    {
                        for (uint i = 0; i < 42; i = i + 1)
                            array[i] = int(i + 5);
                        int result;
                        for (uint i = 0; i < 42; i = i + 1)
                            result = result + array[i];
                        return result;
                    }
                    int foo()
                    {
                        int[42] array;
                        return bar(@array);
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_42 * Constants.WSL_5 + (Constants.WSL_42 * Constants.WSL_41) / Constants.WSL_2);
            return null;
        });

        tests.put("overrideSubscriptStruct", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo {
                        int x;
                        int y;
                    }
                    thread int* operator&[](thread Foo* foo, uint index)
                    {
                        if (index == 0)
                            return &foo->x;
                        if (index == 1)
                            return &foo->y;
                        return null;
                    }
                    int foo()
                    {
                        Foo foo;
                        foo.x = 498;
                        foo.y = 19;
                        return foo[0] + foo[1] * 3;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_498 + Constants.WSL_19 * Constants.WSL_3);
            return null;
        });

        tests.put("overrideSubscriptStructAndDoStores", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo {
                        int x;
                        int y;
                    }
                    thread int* operator&[](thread Foo* foo, uint index)
                    {
                        if (index == 0)
                            return &foo->x;
                        if (index == 1)
                            return &foo->y;
                        return null;
                    }
                    int foo()
                    {
                        Foo foo;
                        foo[0] = 498;
                        foo[1] = 19;
                        return foo.x + foo.y;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_498 + Constants.WSL_19);
            return null;
        });

        tests.put("overrideSubscriptStructAndUsePointers", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo {
                        int x;
                        int y;
                    }
                    thread int* operator&[](thread Foo* foo, uint index)
                    {
                        if (index == 0)
                            return &foo->x;
                        if (index == 1)
                            return &foo->y;
                        return null;
                    }
                    int bar(thread Foo* foo)
                    {
                        return (*foo)[0] + (*foo)[1];
                    }
                    int foo()
                    {
                        Foo foo;
                        foo.x = 498;
                        foo.y = 19;
                        return bar(&foo);
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_498 + Constants.WSL_19);
            return null;
        });

        tests.put("overrideSubscriptStructAndUsePointersIncorrectly", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            struct Foo {
                                int x;
                                int y;
                            }
                            thread int* operator&[](thread Foo* foo, uint index)
                            {
                                if (index == 0)
                                    return &foo->x;
                                if (index == 1)
                                    return &foo->y;
                                return null;
                            }
                            int bar(thread Foo* foo)
                            {
                                return foo[0] + foo[1];
                            }
                            int foo()
                            {
                                Foo foo;
                                foo.x = 498;
                                foo.y = 19;
                                return bar(&foo);
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("makeArrayRefFromLocal", () -> {
            Program program = AllFunction.doPrep("""
                    int bar(thread int[] ref)
                    {
                        return ref[0];
                    }
                    int foo()
                    {
                        int x = 48;
                        return bar(@x);
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_48);
            return null;
        });

        tests.put("makeArrayRefFromPointer", () -> {
            Program program = AllFunction.doPrep("""
                    int bar(thread int[] ref)
                    {
                        return ref[0];
                    }
                    int baz(thread int* ptr)
                    {
                        return bar(@ptr);
                    }
                    int foo()
                    {
                        int x = 48;
                        return baz(&x);
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_48);
            return null;
        });

        tests.put("makeArrayRefFromArrayRef", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int bar(thread int[] ref)
                            {
                                return ref[0];
                            }
                            int baz(thread int[] ptr)
                            {
                                return bar(@ptr);
                            }
                            int foo()
                            {
                                int x = 48;
                                return baz(@x);
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("simpleLength", () -> {
            Program program = AllFunction.doPrep("""
                    uint foo()
                    {
                        double[754] array;
                        return (@array).length;
                    }
                    """);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_754);
            return null;
        });

        tests.put("nonArrayRefArrayLengthSucceed", () -> {
            Program program = AllFunction.doPrep("""
                    uint foo()
                    {
                        double[754] array;
                        return array.length;
                    }
                    """);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_754);
            return null;
        });

        tests.put("nonArrayRefArrayLengthFail", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            thread uint* lengthPtr()
                            {
                                int[42] array;
                                return &(array.length);
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("constexprIsNotLValuePtr", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            thread int* foo<int x>()
                            {
                                return &x;
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("constexprIsNotLValueAssign", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void foo<int x>()
                            {
                                x = 42;
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("constexprIsNotLValueRMW", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void foo<int x>()
                            {
                                x += 42;
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError);
            return null;
        });

        tests.put("assignLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void foo()
                            {
                                double[754] array;
                                (@array).length = 42;
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Have neither ander nor setter"));
            return null;
        });

        tests.put("assignLengthHelper", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            void bar(thread double[] array)
                            {
                                array.length = 42;
                            }
                            void foo()
                            {
                                double[754] array;
                                bar(@array);
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Have neither ander nor setter"));
            return null;
        });

        tests.put("simpleGetter", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo {
                        int x;
                    }
                    int operator.y(Foo foo)
                    {
                        return foo.x;
                    }
                    int foo()
                    {
                        Foo foo;
                        foo.x = 7804;
                        return foo.y;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_7804);
            return null;
        });

        tests.put("simpleSetter", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo {
                        int x;
                    }
                    int operator.y(Foo foo)
                    {
                        return foo.x;
                    }
                    Foo operator.y=(Foo foo, int value)
                    {
                        foo.x = value;
                        return foo;
                    }
                    int foo()
                    {
                        Foo foo;
                        foo.y = 7804;
                        return foo.x;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_7804);
            return null;
        });

        tests.put("genericAccessors", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo<T> {
                                  T x;
                                  T[3] y;
                              }
                              struct Bar<T> {
                                  T x;
                                  T y;
                              }
                              Bar<T> operator.z<T>(Foo<T> foo)
                              {
                                  Bar<T> result;
                                  result.x = foo.x;
                                  result.y = foo.y[1];
                                  return result;
                              }
                              Foo<T> operator.z=<T>(Foo<T> foo, Bar<T> bar)
                              {
                                  foo.x = bar.x;
                                  foo.y[1] = bar.y;
                                  return foo;
                              }
                              T operator.sum<T:Addable>(Foo<T> foo)
                              {
                                  return foo.x + foo.y[0] + foo.y[1] + foo.y[2];
                              }
                              T operator.sum<T:Addable>(Bar<T> bar)
                              {
                                  return bar.x + bar.y;
                              }
                              operator<T> Bar<T>(T x, T y)
                              {
                                  Bar<T> result;
                                  result.x = x;
                                  result.y = y;
                                  return result;
                              }
                              void setup(thread Foo<int>* foo)
                              {
                                  foo->x = 1;
                                  foo->y[0] = 2;
                                  foo->y[1] = 3;
                                  foo->y[2] = 4;
                              }
                              int testSuperBasic()
                              {
                                 Foo<int> foo;
                                 setup(&foo);
                                 return foo.sum;
                              }
                              int testZSetterDidSetY()
                              {
                                  Foo<int> foo;
                                  foo.z = Bar<int>(53, 932);
                                  return foo.y[1];
                              }
                              int testZSetter()
                              {
                                  Foo<int> foo;
                                  foo.z = Bar<int>(53, 932);
                                  return foo.sum;
                              }
                              int testZGetter()
                              {
                                  Foo<int> foo;
                                  // This deliberately does not call setup() just so we test this syntax.
                                  foo.x = 1;
                                  foo.y[0] = 2;
                                  foo.y[1] = 3;
                                  foo.y[2] = 4;
                                  return foo.z.sum;
                              }
                              int testLValueEmulation()
                              {
                                  Foo<int> foo;
                                  setup(&foo);
                                  foo.z.y *= 5;
                                  return foo.sum;
                              }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testSuperBasic", new ArrayList<>(), new ArrayList<>()), 1 + Constants.WSL_2 + Constants.WSL_3 + Constants.WSL_4);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testZSetterDidSetY", new ArrayList<>(), new ArrayList<>()), Constants.WSL_932);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testZSetter", new ArrayList<>(), new ArrayList<>()), Constants.WSL_53 + Constants.WSL_932);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testZGetter", new ArrayList<>(), new ArrayList<>()), 1 + Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testLValueEmulation", new ArrayList<>(), new ArrayList<>()), 1 + Constants.WSL_2 + Constants.WSL_3 * Constants.WSL_5 + Constants.WSL_4);
            return null;
        });

        tests.put("bitSubscriptAccessor", () -> {
            Program program = AllFunction.doPrep("""
                    protocol MyBitmaskable : Equatable {
                                  MyBitmaskable operator&(MyBitmaskable, MyBitmaskable);
                                  MyBitmaskable operator|(MyBitmaskable, MyBitmaskable);
                                  MyBitmaskable operator~(MyBitmaskable);
                                  MyBitmaskable operator<<(MyBitmaskable, uint);
                                  MyBitmaskable operator>>(MyBitmaskable, uint);
                                  operator MyBitmaskable(int);
                              }
                              T maskForBitIndex<T:MyBitmaskable>(uint index)
                              {
                                  return T(1) << index;
                              }
                              bool operator[]<T:MyBitmaskable>(T value, uint index)
                              {
                                  return bool(value & maskForBitIndex<T>(index));
                              }
                              T operator[]=<T:MyBitmaskable>(T value, uint index, bool bit)
                              {
                                  T mask = maskForBitIndex<T>(index);
                                  if (bit)
                                      value |= mask;
                                  else
                                      value &= ~mask;
                                  return value;
                              }
                              uint operator.length(int)
                              {
                                  return 32;
                              }
                              uint operator.length(uint)
                              {
                                  return 32;
                              }
                              int testIntSetBit3()
                              {
                                  int foo;
                                  foo[3] = true;
                                  return foo;
                              }
                              bool testIntSetGetBit5()
                              {
                                  int foo;
                                  foo[5] = true;
                                  return foo[5];
                              }
                              bool testIntGetBit1()
                              {
                                  int foo;
                                  return foo[1];
                              }
                              int testUintSumBits()
                              {
                                  int foo = 42;
                                  int result;
                                  for (uint i = 0; i < foo.length; ++i) {
                                      if (foo[i])
                                          result++;
                                  }
                                  return result;
                              }
                              int testUintSwapBits()
                              {
                                  int foo = 42;
                                  for (uint i = 0; i < foo.length / 2; ++i) {
                                      bool tmp = foo[i];
                                      foo[i] = foo[foo.length - i - 1];
                                      foo[foo.length - i - 1] = tmp;
                                  }
                                  return foo;
                              }
                              struct Foo {
                                  uint f;
                                  uint g;
                              }
                              operator Foo(uint f, uint g)
                              {
                                  Foo result;
                                  result.f = f;
                                  result.g = g;
                                  return result;
                              }
                              int operator.h(Foo foo)
                              {
                                  return int((foo.f & 0xffff) | ((foo.g & 0xffff) << 16));
                              }
                              Foo operator.h=(Foo foo, int value)
                              {
                                  foo.f &= ~0xffffu;
                                  foo.f |= uint(value) & 0xffff;
                                  foo.g &= ~0xffffu;
                                  foo.g |= (uint(value) >> 16) & 0xffff;
                                  return foo;
                              }
                              int testLValueEmulation()
                              {
                                  Foo foo;
                                  foo.f = 42;
                                  foo.g = 37;
                                  for (uint i = 0; i < foo.h.length; ++i)
                                      foo.h[i] ^= true;
                                  return int(foo.f + foo.g);
                              }
                              struct Bar {
                                  Foo a;
                                  Foo b;
                              }
                              Foo operator.c(Bar bar)
                              {
                                  return Foo(uint(bar.a.h), uint(bar.b.h));
                              }
                              Bar operator.c=(Bar bar, Foo foo)
                              {
                                  bar.a.h = int(foo.f);
                                  bar.b.h = int(foo.g);
                                  return bar;
                              }
                              int testCrazyLValueEmulation()
                              {
                                  Bar bar;
                                  bar.a.f = 1;
                                  bar.a.g = 2;
                                  bar.b.f = 3;
                                  bar.b.g = 4;
                                  for (uint i = 0; i < bar.c.h.length; i += 2)
                                      bar.c.h[i] ^= true;
                                  return int(bar.a.f + bar.a.g + bar.b.f + bar.b.g);
                              }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testIntSetBit3", new ArrayList<>(), new ArrayList<>()), Constants.WSL_8);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testIntSetGetBit5", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testIntGetBit1", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testUintSumBits", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testUintSwapBits", new ArrayList<>(), new ArrayList<>()), Constants.WSL_1409286144);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testLValueEmulation", new ArrayList<>(), new ArrayList<>()), Constants.WSL_130991);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testCrazyLValueEmulation", new ArrayList<>(), new ArrayList<>()), Constants.WSL_43696);
            return null;
        });

        tests.put("nestedSubscriptLValueEmulationSimple", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo {
                        int[7] array;
                    }
                    int operator[](Foo foo, uint index)
                    {
                        return foo.array[index];
                    }
                    Foo operator[]=(Foo foo, uint index, int value)
                    {
                        foo.array[index] = value;
                        return foo;
                    }
                    uint operator.length(Foo foo)
                    {
                        return foo.array.length;
                    }
                    int sum(Foo foo)
                    {
                        int result = 0;
                        for (uint i = foo.length; i--;)
                            result += foo[i];
                        return result;
                    }
                    struct Bar {
                        Foo[6] array;
                    }
                    uint operator.length(Bar bar)
                    {
                        return bar.array.length;
                    }
                    Foo operator[](Bar bar, uint index)
                    {
                        return bar.array[index];
                    }
                    Bar operator[]=(Bar bar, uint index, Foo value)
                    {
                        bar.array[index] = value;
                        return bar;
                    }
                    int sum(Bar bar)
                    {
                        int result = 0;
                        for (uint i = bar.length; i--;)
                            result += sum(bar[i]);
                        return result;
                    }
                    struct Baz {
                        Bar[5] array;
                    }
                    Bar operator[](Baz baz, uint index)
                    {
                        return baz.array[index];
                    }
                    Baz operator[]=(Baz baz, uint index, Bar value)
                    {
                        baz.array[index] = value;
                        return baz;
                    }
                    uint operator.length(Baz baz)
                    {
                        return baz.array.length;
                    }
                    int sum(Baz baz)
                    {
                        int result = 0;
                        for (uint i = baz.length; i--;)
                            result += sum(baz[i]);
                        return result;
                    }
                    void setValues(thread Baz* baz)
                    {
                        for (uint i = baz->length; i--;) {
                            for (uint j = (*baz)[i].length; j--;) {
                                for (uint k = (*baz)[i][j].length; k--;)
                                    (*baz)[i][j][k] = int(i + j + k);
                            }
                        }
                    }
                    int testSetValuesAndSum()
                    {
                        Baz baz;
                        setValues(&baz);
                        return sum(baz);
                    }
                    int testSetValuesMutateValuesAndSum()
                    {
                        Baz baz;
                        setValues(&baz);
                        for (uint i = baz.length; i--;) {
                            for (uint j = baz[i].length; j--;) {
                                for (uint k = baz[i][j].length; k--;)
                                    baz[i][j][k] *= int(k);
                            }
                        }
                        return sum(baz);
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testSetValuesAndSum", new ArrayList<>(), new ArrayList<>()), Constants.WSL_1575);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testSetValuesMutateValuesAndSum", new ArrayList<>(), new ArrayList<>()), Constants.WSL_5565);
            return null;
        });

        tests.put("nestedSubscriptLValueEmulationGeneric", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo<T> {
                        T[7] array;
                    }
                    T operator[]<T>(Foo<T> foo, uint index)
                    {
                        return foo.array[index];
                    }
                    Foo<T> operator[]=<T>(Foo<T> foo, uint index, T value)
                    {
                        foo.array[index] = value;
                        return foo;
                    }
                    uint operator.length<T>(Foo<T> foo)
                    {
                        return foo.array.length;
                    }
                    protocol MyAddable {
                        MyAddable operator+(MyAddable, MyAddable);
                    }
                    T sum<T:MyAddable>(Foo<T> foo)
                    {
                        T result;
                        for (uint i = foo.length; i--;)
                            result += foo[i];
                        return result;
                    }
                    struct Bar<T> {
                        Foo<T>[6] array;
                    }
                    uint operator.length<T>(Bar<T> bar)
                    {
                        return bar.array.length;
                    }
                    Foo<T> operator[]<T>(Bar<T> bar, uint index)
                    {
                        return bar.array[index];
                    }
                    Bar<T> operator[]=<T>(Bar<T> bar, uint index, Foo<T> value)
                    {
                        bar.array[index] = value;
                        return bar;
                    }
                    T sum<T:MyAddable>(Bar<T> bar)
                    {
                        T result;
                        for (uint i = bar.length; i--;)
                            result += sum(bar[i]);
                        return result;
                    }
                    struct Baz<T> {
                        Bar<T>[5] array;
                    }
                    Bar<T> operator[]<T>(Baz<T> baz, uint index)
                    {
                        return baz.array[index];
                    }
                    Baz<T> operator[]=<T>(Baz<T> baz, uint index, Bar<T> value)
                    {
                        baz.array[index] = value;
                        return baz;
                    }
                    uint operator.length<T>(Baz<T> baz)
                    {
                        return baz.array.length;
                    }
                    T sum<T:MyAddable>(Baz<T> baz)
                              {
                                  T result;
                                  for (uint i = baz.length; i--;) {
                                      for (uint j = baz[i].length; j--;) {
                                          for (uint k = baz[i][j].length; k--;)
                                                result += baz[i][j][k];
                                          }
                                  }
                                  return result;
                              }
                    protocol MyConvertibleFromUint {
                        operator MyConvertibleFromUint(uint);
                    }
                    protocol SetValuable : MyAddable, MyConvertibleFromUint { }
                    void setValues<T:SetValuable>(thread Baz<T>* baz)
                    {
                        for (uint i = baz->length; i--;) {
                            for (uint j = (*baz)[i].length; j--;) {
                                for (uint k = (*baz)[i][j].length; k--;)
                                    (*baz)[i][j][k] = T(i + j + k);
                            }
                        }
                    }
                    int testSetValuesAndSum()
                    {
                        Baz<int> baz;
                        setValues(&baz);
                        return sum(baz);
                    }
                    int testSetValuesMutateValuesAndSum()
                    {
                        Baz<int> baz;
                        setValues(&baz);
                        for (uint i = baz.length; i--;) {
                            for (uint j = baz[i].length; j--;) {
                                for (uint k = baz[i][j].length; k--;)
                                    baz[i][j][k] *= int(k);
                            }
                        }
                        return sum(baz);
                    }
                    """);
            Constants.nub = 0;
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testSetValuesAndSum", new ArrayList<>(), new ArrayList<>()), Constants.WSL_1575);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testSetValuesMutateValuesAndSum", new ArrayList<>(), new ArrayList<>()), Constants.WSL_5565);
            return null;
        });

        tests.put("boolBitAnd", () -> {
            Program program = AllFunction.doPrep("""
                    bool foo(bool a, bool b)
                    {
                        return a & b;
                    }
                    """);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, false),AllFunction.makeBool(program, false)))), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, true),AllFunction.makeBool(program, false)))), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, false),AllFunction.makeBool(program, true)))), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, true),AllFunction.makeBool(program, true)))), true);

            return null;
        });

        tests.put("boolBitOr", () -> {
            Program program = AllFunction.doPrep("""
                    bool foo(bool a, bool b)
                    {
                        return a | b;
                    }
                    """);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, false),AllFunction.makeBool(program, false)))), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, true),AllFunction.makeBool(program, false)))), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, false),AllFunction.makeBool(program, true)))), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, false),AllFunction.makeBool(program, true)))), true);
            return null;
        });

        tests.put("boolBitXor", () -> {
            Program program = AllFunction.doPrep("""
                    bool foo(bool a, bool b)
                    {
                        return a ^ b;
                    }
                    """);

            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, false),AllFunction.makeBool(program, false)))), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, true),AllFunction.makeBool(program, false)))), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, false),AllFunction.makeBool(program, true)))), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, true),AllFunction.makeBool(program, true)))), false);
            return null;
        });

        tests.put("boolBitNot", () -> {
            Program program = AllFunction.doPrep("""
                    bool foo(bool a)
                    {
                        return ~a;
                    }
                    """);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, false)))), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeBool(program, true)))), false);
            return null;
        });

        tests.put("intBitAnd", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int a, int b)
                    {
                        return a & b;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 1),AllFunction.makeInt(program, Constants.WSL_7)))), 1);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_65535),AllFunction.makeInt(program, Constants.WSL_42)))), Constants.WSL_42);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, -1),AllFunction.makeInt(program, Constants.WSLNEGATIVE_7)))), Constants.WSLNEGATIVE_7);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 0),AllFunction.makeInt(program, Constants.WSL_85732)))), 0);
            return null;
        });

        tests.put("intBitOr", () -> {
            Program program = AllFunction.doPrep("""
                            
                        int foo(int a, int b)
                        {
                            return a | b;
                        }
                            
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 1),AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSL_7);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_65535),AllFunction.makeInt(program, Constants.WSL_42)))), Constants.WSL_65535);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, -1),AllFunction.makeInt(program, Constants.WSLNEGATIVE_7)))), -1);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 0),AllFunction.makeInt(program, Constants.WSL_85732)))), Constants.WSL_85732);
            return null;
        });

        tests.put("intBitXor", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int a, int b)
                    {
                        return a ^ b;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 1),AllFunction.makeInt(program, Constants.WSL_7)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_65535),AllFunction.makeInt(program, Constants.WSL_42)))), Constants.WSL_65493);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, -1),AllFunction.makeInt(program, Constants.WSLNEGATIVE_7)))), Constants.WSL_6);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 0),AllFunction.makeInt(program, Constants.WSL_85732)))), Constants.WSL_85732);
            return null;
        });

        tests.put("intBitNot", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int a)
                    {
                          return ~a;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 1)))), Constants.WSLNEGATIVE_2);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_65535)))), Constants.WSLNEGATIVE_65536);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, -1)))), 0);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 0)))), -1);
            return null;
        });

        tests.put("intLShift", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int a, uint b)
                    {
                        return a << b;
                    }
                    """);

            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 1),AllFunction.makeUint(program, Constants.WSL_7)))), Constants.WSL_128);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_65535),AllFunction.makeUint(program, Constants.WSL_2)))), Constants.WSL_262140);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, -1),AllFunction.makeUint(program, Constants.WSL_5)))), Constants.WSLNEGATIVE_32);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 0),AllFunction.makeUint(program, Constants.WSL_3)))), 0);
            return null;
        });

        tests.put("intRShift", () -> {
            Program program = AllFunction.doPrep("""
                    int foo(int a, uint b)
                    {
                        return a >> b;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 1),AllFunction.makeUint(program, Constants.WSL_7)))), 0);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_65535),AllFunction.makeUint(program, Constants.WSL_2)))), Constants.WSL_16383);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, -1),AllFunction.makeUint(program, Constants.WSL_5)))), -1);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 0),AllFunction.makeUint(program, Constants.WSL_3)))), 0);
            return null;
        });

        tests.put("uintBitAnd", () -> {
            Program program = AllFunction.doPrep("""
                    uint foo(uint a, uint b)
                    {
                        return a & b;
                    }
                    """);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, 1),AllFunction.makeUint(program, Constants.WSL_7)))), 1);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, Constants.WSL_65535),AllFunction.makeUint(program, Constants.WSL_42)))), Constants.WSL_42);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, -1),AllFunction.makeUint(program, Constants.WSLNEGATIVE_7)))), (int) Constants.WSL_4294967289);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, 0),AllFunction.makeUint(program, Constants.WSL_85732)))), 0);
            return null;
        });

        tests.put("uintBitOr", () -> {
            Program program = AllFunction.doPrep("""
                    uint foo(uint a, uint b)
                    {
                        return a | b;
                    }
                    """);

            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, 1),AllFunction.makeUint(program, Constants.WSL_7)))), Constants.WSL_7);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, Constants.WSL_65535),AllFunction.makeUint(program, Constants.WSL_42)))), Constants.WSL_65535);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, -1),AllFunction.makeUint(program, Constants.WSLNEGATIVE_7)))), (int) Constants.WSL_4294967295);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, 0),AllFunction.makeUint(program, Constants.WSL_85732)))), Constants.WSL_85732);
            return null;
        });

        tests.put("uintBitXor", () -> {
            Program program = AllFunction.doPrep("""
                    uint foo(uint a, uint b)
                    {
                        return a ^ b;
                    }
                    """);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, 1),AllFunction.makeUint(program, Constants.WSL_7)))), Constants.WSL_6);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, Constants.WSL_65535),AllFunction.makeUint(program, Constants.WSL_42)))), Constants.WSL_65493);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, -1),AllFunction.makeUint(program, Constants.WSLNEGATIVE_7)))), Constants.WSL_6);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, 0),AllFunction.makeUint(program, Constants.WSL_85732)))), Constants.WSL_85732);
            return null;
        });

        tests.put("uintBitNot", () -> {
            Program program = AllFunction.doPrep("""
                    uint foo(uint a)
                    {
                        return ~a;
                    }
                    """);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, 1)))), (int) Constants.WSL_4294967294);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, Constants.WSL_65535)))), (int) Constants.WSL_4294901760);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, -1)))), 0);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, 0)))), (int) Constants.WSL_4294967295);
            return null;
        });

        tests.put("uintLShift", () -> {
            Program program = AllFunction.doPrep("""
                    uint foo(uint a, uint b)
                    {
                        return a << b;
                    }
                    """);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, 1),AllFunction.makeUint(program, Constants.WSL_7)))), Constants.WSL_128);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, Constants.WSL_65535),AllFunction.makeUint(program, Constants.WSL_2)))), Constants.WSL_262140);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, -1),AllFunction.makeUint(program, Constants.WSL_5)))), (int) Constants.WSL_4294967264);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, 0),AllFunction.makeUint(program, Constants.WSL_3)))), 0);
            return null;
        });

        tests.put("uintRShift", () -> {
            Program program = AllFunction.doPrep("""
                    uint foo(uint a, uint b)
                    {
                        return a >> b;
                    }
                    """);

            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, 1),AllFunction.makeUint(program, Constants.WSL_7)))), 0);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, Constants.WSL_65535),AllFunction.makeUint(program, Constants.WSL_2)))), Constants.WSL_16383);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, -1),AllFunction.makeUint(program, Constants.WSL_5)))), Constants.WSL_134217727);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint(program, 0),AllFunction.makeUint(program, Constants.WSL_3)))), 0);
            return null;
        });

        tests.put("uint8BitAnd", () -> {
            Program program = AllFunction.doPrep("""
                    uint8 foo(uint8 a, uint8 b)
                    {
                        return a & b;
                    }
                    """);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, 1),AllFunction.makeUint8(program, Constants.WSL_7)))), 1);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, Constants.WSL_65535),AllFunction.makeUint8(program, Constants.WSL_42)))), Constants.WSL_42);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, -1),AllFunction.makeUint8(program, Constants.WSLNEGATIVE_7)))), Constants.WSL_249);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, 0),AllFunction.makeUint8(program, Constants.WSL_85732)))), 0);
            return null;
        });

        tests.put("uint8BitOr", () -> {
            Program program = AllFunction.doPrep("""
                    uint8 foo(uint8 a, uint8 b)
                    {
                        return a | b;
                    }
                    """);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, 1),AllFunction.makeUint8(program, Constants.WSL_7)))), Constants.WSL_7);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, Constants.WSL_65535),AllFunction.makeUint8(program, Constants.WSL_42)))), Constants.WSL_255);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, -1),AllFunction.makeUint8(program, Constants.WSLNEGATIVE_7)))), Constants.WSL_255);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, 0),AllFunction.makeUint8(program, Constants.WSL_85732)))), Constants.WSL_228);
            return null;
        });

        tests.put("uint8BitXor", () -> {
            Program program = AllFunction.doPrep("""
                    uint8 foo(uint8 a, uint8 b)
                    {
                        return a ^ b;
                    }
                    """);

            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, 1),AllFunction.makeUint8(program, Constants.WSL_7)))), Constants.WSL_6);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, Constants.WSL_65535),AllFunction.makeUint8(program, Constants.WSL_42)))), Constants.WSL_213);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, -1),AllFunction.makeUint8(program, Constants.WSLNEGATIVE_7)))), Constants.WSL_6);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, 0),AllFunction.makeUint8(program, Constants.WSL_85732)))), Constants.WSL_228);
            return null;
        });

        tests.put("uint8BitNot", () -> {
            Program program = AllFunction.doPrep("""
                    uint8 foo(uint8 a)
                    {
                        return ~a;
                    }
                    """);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, 1)))), (int) Constants.WSL_254);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, Constants.WSL_65535)))), 0);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, -1)))), 0);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, 0)))), (int) Constants.WSL_255);
            return null;
        });

        tests.put("uint8LShift", () -> {
            Program program = AllFunction.doPrep("""
                            
                        uint8 foo(uint8 a, uint b)
                        {
                            return a << b;
                        }
                            
                    """);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, 1),AllFunction.makeUint(program, Constants.WSL_7)))), Constants.WSL_128);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, Constants.WSL_65535),AllFunction.makeUint(program, Constants.WSL_2)))), Constants.WSL_252);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, -1),AllFunction.makeUint(program, Constants.WSL_5)))), Constants.WSL_224);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, 0),AllFunction.makeUint(program, Constants.WSL_3)))), 0);
            return null;
        });

        tests.put("uint8RShift", () -> {
            Program program = AllFunction.doPrep("""
                            
                        uint8 foo(uint8 a, uint b)
                        {
                            return a >> b;
                        }
                            
                    """);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, 1),AllFunction.makeUint(program, Constants.WSL_7)))), 0);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, Constants.WSL_65535),AllFunction.makeUint(program, Constants.WSL_2)))), Constants.WSL_255);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, -1),AllFunction.makeUint(program, Constants.WSL_5)))), Constants.WSL_255);
            AllFunction.checkUint8(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, 0),AllFunction.makeUint(program, Constants.WSL_3)))), 0);
            return null;
        });

        tests.put("floatMath", () -> {

            Program program = AllFunction.doPrep("""
          bool foo()
          {
              return 42.5 == 42.5;
          }
          bool foo2()
          {
              return 42.5f == 42.5;
          }
          bool foo3()
          {
              return 42.5 == 42.5f;
          }
          bool foo4()
          {
              return 42.5f == 42.5f;
          }
          bool foo5()
          {
              return 42.5d == 42.5d;
          }
          float bar(float x)
          {
              return x;
          }
          float foo6()
          {
              return bar(7.5);
          }
          float foo7()
          {
              return bar(7.5f);
          }
          float foo8()
          {
              return bar(7.5d);
          }
          float foo9()
          {
              return float(7.5);
          }
          float foo10()
          {
              return float(7.5f);
          }
          float foo11()
          {
              return float(7.5d);
          }
          float foo12()
          {
              return float(7);
          }
          float foo13()
          {
              double x = 7.5d;
              return float(x);
          }
          double foo14()
          {
              double x = 7.5f;
              return double(x);
          }
        """);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo2", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo3", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo4", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo5", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkFloat(program, AllFunction.callFunction(program, "foo6", new ArrayList<>(), new ArrayList<>()), Constants.WSLDECIMAL_7);
            AllFunction.checkFloat(program, AllFunction.callFunction(program, "foo7", new ArrayList<>(), new ArrayList<>()), Constants.WSLDECIMAL_7);
            AllFunction.checkFloat(program, AllFunction.callFunction(program, "foo8", new ArrayList<>(), new ArrayList<>()), Constants.WSLDECIMAL_7);
            AllFunction.checkFloat(program, AllFunction.callFunction(program, "foo9", new ArrayList<>(), new ArrayList<>()), Constants.WSLDECIMAL_7);
            AllFunction.checkFloat(program, AllFunction.callFunction(program, "foo10", new ArrayList<>(), new ArrayList<>()), Constants.WSLDECIMAL_7);
            AllFunction.checkFloat(program, AllFunction.callFunction(program, "foo11", new ArrayList<>(), new ArrayList<>()), Constants.WSLDECIMAL_7);
            AllFunction.checkFloat(program, AllFunction.callFunction(program, "foo12", new ArrayList<>(), new ArrayList<>()), Constants.WSL_7);
            AllFunction.checkFloat(program, AllFunction.callFunction(program, "foo13", new ArrayList<>(), new ArrayList<>()), Constants.WSLDECIMAL_7);
            AllFunction.checkDouble(program, AllFunction.callFunction(program, "foo14", new ArrayList<>(), new ArrayList<>()), Constants.WSLDECIMAL_7);

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""

                int bar(int x)
                {
                    return x;
                }
                int foo()
                {
                    bar(4.);
                }

            """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""

                int bar(int x)
                {
                    return x;
                }
                int foo()
                {
                    bar(4.d);
                }

            """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""

                int bar(int x)
                {
                    return x;
                }
                int foo()
                {
                    bar(4.f);
                }

            """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""

                uint bar(uint x)
                {
                    return x;
                }
                int foo()
                {
                    bar(4.);
                }

            """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""

                uint bar(uint x)
                {
                    return x;
                }
                int foo()
                {
                    bar(4.d);
                }

            """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""

                                                         uint bar(uint x)
                                                                         {
                                                                             return x;
                                                                         }
                                                                         int foo()
                                                                         {
                                                                             bar(4.f);
                                                                         }

                            """),
                    (RuntimeException e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""

                                 float bar(float x)
                                 {
                                     return x;
                                 }
                                 void foo()
                                 {
                                     bar(16777217.d);
                                 }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""

              float bar(float x)
              {
                  return x;
              }
              float foo()
              {
                  double x = 7.;
                  return bar(x);
              }

            """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""

                                                   float foo()
                                                   {
                                                       double x = 7.;
                                                       return x;
                                                   }

                            """),
                    (RuntimeException e) -> e instanceof WTypeError
            );

            return null;
        });

        tests.put("genericCastInfer", () -> {
            Program program = AllFunction.doPrep("""
                    struct Complex<T> {
                        T real;
                        T imag;
                    }
                    operator<T> Complex<T>(T real, T imag)
                    {
                        Complex<T> result;
                        result.real = real;
                        result.imag = imag;
                        return result;
                    }
                    int foo()
                    {
                        Complex<int> x = Complex<int>(1, 2);
                        return x.real + x.imag;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            return null;
        });

        tests.put("booleanMath", () -> {
            Program program = AllFunction.doPrep("""
                    bool foo()
                    {
                        return true && true;
                    }
                    bool foo2()
                    {
                        return true && false;
                    }
                    bool foo3()
                    {
                        return false && true;
                    }
                    bool foo4()
                    {
                        return false && false;
                    }
                    bool foo5()
                    {
                        return true || true;
                    }
                    bool foo6()
                    {
                        return true || false;
                    }
                    bool foo7()
                    {
                        return false || true;
                    }
                    bool foo8()
                    {
                        return false || false;
                    }
                    """);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo2", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo3", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo4", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo5", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo6", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo7", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo8", new ArrayList<>(), new ArrayList<>()), false);

            return null;
        });

        tests.put("typedefArray", () -> {
            Program program = AllFunction.doPrep("""
                          typedef ArrayTypedef = int[2];
                          int foo()
                          {
                              ArrayTypedef arrayTypedef;
                              return arrayTypedef[0];
                          }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), 0);
            return null;
        });

        tests.put("shaderTypes", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            struct Foo {
                               float4 x;
                            }
                            vertex Foo bar()
                            {
                                Foo result;
                                result.x = float4();
                                return result;
                            }
                            Foo foo() {
                                return bar();
                            }
                            """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                            vertex float bar()
                            {
                                return 4.;
                            }
                              """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                            struct Foo {
                                float4 x;
                            }
                            vertex Foo bar(device Foo* x)
                            {
                                return Foo();
                            }
                              """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                            struct Boo {
                                float4 x;
                            }
                            struct Foo {
                                float4 x;
                                device Boo* y;
                            }
                            vertex Foo bar()
                            {
                                return Foo();
                            }
                              """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                            struct Foo {
                                float4 x;
                            }
                            struct Boo {
                                device Foo* y;
                            }
                            vertex Foo bar(Boo b)
                            {
                                return Foo();
                            }
                              """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                            struct Foo {
                                float4 x;
                            }
                            vertex Foo bar(device Foo* x)
                            {
                                return Foo();
                            }
                              """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                            struct Foo {
                                float4 x;
                            }
                            fragment Foo bar(Foo foo)
                            {
                                return Foo();
                            }
                              """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                            struct Foo {
                                float4 x;
                            }
                            fragment Foo bar(device Foo* stageIn)
                            {
                                return Foo();
                            }
                              """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                            struct Boo {
                                float4 x;
                            }
                            struct Foo {
                                float4 x;
                                device Boo* y;
                            }
                            fragment Boo bar(Foo stageIn)
                            {
                                return boo();
                            }
                              """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                            struct Boo {
                                float4 x;
                            }
                            struct Foo {
                                float4 x;
                                device Boo* y;
                            }
                            fragment Foo bar(Boo stageIn)
                            {
                                return Foo();
                            }
                              """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("builtinVectors", () -> {
            Program program = AllFunction.doPrep("""
        
            int foo()
            {
                int2 a = int2(3, 4);
                return a[0];
            }
            int foo2()
            {
                int2 a = int2(3, 4);
                int3 b = int3(a, 5);
                return b[1];
            }
            int foo3()
            {
                int3 a = int3(3, 4, 5);
                int4 b = int4(6, a);
                return b[1];
            }
            int foo4()
            {
                int2 a = int2(3, 4);
                int2 b = int2(5, 6);
                int4 c = int4(a, b);
                return c[2];
            }
            bool foo5()
            {
                int4 a = int4(3, 4, 5, 6);
                int2 b = int2(4, 5);
                int4 c = int4(3, b, 6);
                return a == c;
            }
            bool foo6()
            {
                int2 a = int2(4, 5);
                int3 b = int3(3, a);
                int3 c = int3(3, 4, 6);
                return b == c;
            }
            uint foou()
            {
                uint2 a = uint2(3, 4);
                return a[0];
            }
            uint foou2()
            {
                uint2 a = uint2(3, 4);
                uint3 b = uint3(a, 5);
                return b[1];
            }
            uint foou3()
            {
                uint3 a = uint3(3, 4, 5);
                uint4 b = uint4(6, a);
                return b[1];
            }
            uint foou4()
            {
                uint2 a = uint2(3, 4);
                uint2 b = uint2(5, 6);
                uint4 c = uint4(a, b);
                return c[2];
            }
            bool foou5()
            {
                uint4 a = uint4(3, 4, 5, 6);
                uint2 b = uint2(4, 5);
                uint4 c = uint4(3, b, 6);
                return a == c;
            }
            bool foou6()
            {
                uint2 a = uint2(4, 5);
                uint3 b = uint3(3, a);
                uint3 c = uint3(3, 4, 6);
                return b == c;
            }
            float foof()
            {
                float2 a = float2(3., 4.);
                return a[0];
            }
            float foof2()
            {
                float2 a = float2(3., 4.);
                float3 b = float3(a, 5.);
                return b[1];
            }
            float foof3()
            {
                float3 a = float3(3., 4., 5.);
                float4 b = float4(6., a);
                return b[1];
            }
            float foof4()
            {
                float2 a = float2(3., 4.);
                float2 b = float2(5., 6.);
                float4 c = float4(a, b);
                return c[2];
            }
            bool foof5()
            {
                float4 a = float4(3., 4., 5., 6.);
                float2 b = float2(4., 5.);
                float4 c = float4(3., b, 6.);
                return a == c;
            }
            bool foof6()
            {
                float2 a = float2(4., 5.);
                float3 b = float3(3., a);
                float3 c = float3(3., 4., 6.);
                return b == c;
            }
            double food()
            {
                double2 a = double2(3., 4.);
                return a[0];
            }
            double food2()
            {
                double2 a = double2(3., 4.);
                double3 b = double3(a, 5.);
                return b[1];
            }
            double food3()
            {
                double3 a = double3(3., 4., 5.);
                double4 b = double4(6., a);
                return b[1];
            }
            double food4()
            {
                double2 a = double2(3., 4.);
                double2 b = double2(5., 6.);
                double4 c = double4(a, b);
                return c[2];
            }
            bool food5()
            {
                double4 a = double4(3., 4., 5., 6.);
                double2 b = double2(4., 5.);
                double4 c = double4(3., b, 6.);
                return a == c;
            }
            bool food6()
            {
                double2 a = double2(4., 5.);
                double3 b = double3(3., a);
                double3 c = double3(3., 4., 6.);
                return b == c;
            }
        
        """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo2", new ArrayList<>(), new ArrayList<>()), Constants.WSL_4);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo3", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo4", new ArrayList<>(), new ArrayList<>()), Constants.WSL_5);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo5", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foo6", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foou", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foou2", new ArrayList<>(), new ArrayList<>()), Constants.WSL_4);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foou3", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "foou4", new ArrayList<>(), new ArrayList<>()), Constants.WSL_5);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foou5", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foou6", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkFloat(program, AllFunction.callFunction(program, "foof", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkFloat(program, AllFunction.callFunction(program, "foof2", new ArrayList<>(), new ArrayList<>()), Constants.WSL_4);
            AllFunction.checkFloat(program, AllFunction.callFunction(program, "foof3", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkFloat(program, AllFunction.callFunction(program, "foof4", new ArrayList<>(), new ArrayList<>()), Constants.WSL_5);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foof5", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "foof6", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkDouble(program, AllFunction.callFunction(program, "food", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkDouble(program, AllFunction.callFunction(program, "food2", new ArrayList<>(), new ArrayList<>()), Constants.WSL_4);
            AllFunction.checkDouble(program, AllFunction.callFunction(program, "food3", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkDouble(program, AllFunction.callFunction(program, "food4", new ArrayList<>(), new ArrayList<>()), Constants.WSL_5);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "food5", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "food6", new ArrayList<>(), new ArrayList<>()), false);
            return null;
        });

        tests.put("instantiateStructInStruct", () -> {
            Program program = AllFunction.doPrep("""
                     struct Bar<T> {
                         T x;
                     }
                     struct Foo {
                         Bar<int> x;
                     }
                     int foo()
                     {
                         Foo x;
                         x.x.x = 42;
                         x.x.x++;
                         return x.x.x;
                     } 
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_43);
            return null;
        });

        tests.put("instantiateStructInStructWithInt2", () -> {
            Program program = AllFunction.doPrep("""
                     struct Foo {
                            int2 x;
                        }
                        int foo()
                        {
                            Foo x;
                            x.x.x = 42;
                            x.x.x++;
                            return x.x.x;
                        }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_43);
            return null;
        });

        tests.put("simpleEnum", () -> {
            Program program = AllFunction.doPrep("""
                     enum Foo {
                           War,
                           Famine,
                           Pestilence,
                           Death
                       }
                       Foo war()
                       {
                           return Foo.War;
                       }
                       Foo famine()
                       {
                           return Foo.Famine;
                       }
                       Foo pestilence()
                       {
                           return Foo.Pestilence;
                       }
                       Foo death()
                       {
                           return Foo.Death;
                       }
                       bool equals(Foo a, Foo b)
                       {
                           return a == b;
                       }
                       bool notEquals(Foo a, Foo b)
                       {
                           return a != b;
                       }
                       bool testSimpleEqual()
                       {
                           return equals(Foo.War, Foo.War);
                       }
                       bool testAnotherEqual()
                       {
                           return equals(Foo.Pestilence, Foo.Pestilence);
                       }
                       bool testNotEqual()
                       {
                           return equals(Foo.Famine, Foo.Death);
                       }
                       bool testSimpleNotEqual()
                       {
                           return notEquals(Foo.War, Foo.War);
                       }
                       bool testAnotherNotEqual()
                       {
                           return notEquals(Foo.Pestilence, Foo.Pestilence);
                       }
                       bool testNotNotEqual()
                       {
                           return notEquals(Foo.Famine, Foo.Death);
                       }
                       int intWar()
                       {
                           return int(war());
                       }
                       int intFamine()
                       {
                           return int(famine());
                       }
                       int intPestilence()
                       {
                           return int(pestilence());
                       }
                       int intDeath()
                       {
                           return int(death());
                       }
                       int warValue()
                       {
                           return war().value;
                       }
                       int famineValue()
                       {
                           return famine().value;
                       }
                       int pestilenceValue()
                       {
                           return pestilence().value;
                       }
                       int deathValue()
                       {
                           return death().value;
                       }
                       int warValueLiteral()
                       {
                           return Foo.War.value;
                       }
                       int famineValueLiteral()
                       {
                           return Foo.Famine.value;
                       }
                       int pestilenceValueLiteral()
                       {
                           return Foo.Pestilence.value;
                       }
                       int deathValueLiteral()
                       {
                           return Foo.Death.value;
                       }
                       Foo intWarBackwards()
                       {
                           return Foo(intWar());
                       }
                       Foo intFamineBackwards()
                       {
                           return Foo(intFamine());
                       }
                       Foo intPestilenceBackwards()
                       {
                           return Foo(intPestilence());
                       }
                       Foo intDeathBackwards()
                       {
                           return Foo(intDeath());
                       }
                    """);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "war", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "famine", new ArrayList<>(), new ArrayList<>()), 1);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "pestilence", new ArrayList<>(), new ArrayList<>()), Constants.WSL_2);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "death", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testSimpleEqual", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testAnotherEqual", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testNotEqual", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testSimpleNotEqual", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testAnotherNotEqual", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testNotNotEqual", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "intWar", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "intFamine", new ArrayList<>(), new ArrayList<>()), 1);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "intPestilence", new ArrayList<>(), new ArrayList<>()), Constants.WSL_2);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "intDeath", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "warValue", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "famineValue", new ArrayList<>(), new ArrayList<>()), 1);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "pestilenceValue", new ArrayList<>(), new ArrayList<>()), Constants.WSL_2);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "deathValue", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "warValueLiteral", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "famineValueLiteral", new ArrayList<>(), new ArrayList<>()), 1);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "pestilenceValueLiteral", new ArrayList<>(), new ArrayList<>()), Constants.WSL_2);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "deathValueLiteral", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "intWarBackwards", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "intFamineBackwards", new ArrayList<>(), new ArrayList<>()), 1);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "intPestilenceBackwards", new ArrayList<>(), new ArrayList<>()), Constants.WSL_2);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "intDeathBackwards", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            return null;
        });

        tests.put("enumWithManualValues", () -> {
            Program program = AllFunction.doPrep("""
                     enum Foo {
                           War = 72,
                           Famine = 0,
                           Pestilence = 23,
                           Death = -42
                       }
                       Foo war()
                       {
                           return Foo.War;
                       }
                       Foo famine()
                       {
                           return Foo.Famine;
                       }
                       Foo pestilence()
                       {
                           return Foo.Pestilence;
                       }
                       Foo death()
                       {
                           return Foo.Death;
                       }
                    """);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "war", new ArrayList<>(), new ArrayList<>()), Constants.WSL_72);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "famine", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "pestilence", new ArrayList<>(), new ArrayList<>()), Constants.WSL_23);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "death", new ArrayList<>(), new ArrayList<>()), Constants.WSLNEGATIVE_42);
            return null;
        });

        tests.put("enumWithoutZero", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                        enum Foo {
                                           War = 72,
                                           Famine = 64,
                                           Pestilence = 23,
                                           Death = -42
                                        }
                                    """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("enumDuplicates", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                        enum Foo {
                                            War = -42,
                                            Famine = 0,
                                            Pestilence = 23,
                                            Death = -42
                                        }
                                    """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("enumWithSomeManualValues", () -> {
            Program program = AllFunction.doPrep("""
                     enum Foo {
                                      War = 72,
                                      Famine,
                                      Pestilence = 0,
                                      Death
                                  }
                                  Foo war()
                                  {
                                      return Foo.War;
                                  }
                                  Foo famine()
                                  {
                                      return Foo.Famine;
                                  }
                                  Foo pestilence()
                                  {
                                      return Foo.Pestilence;
                                  }
                                  Foo death()
                                  {
                                      return Foo.Death;
                                  }
                    """);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "war", new ArrayList<>(), new ArrayList<>()), Constants.WSL_72);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "famine", new ArrayList<>(), new ArrayList<>()), Constants.WSL_73);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "pestilence", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "death", new ArrayList<>(), new ArrayList<>()), 1);
            return null;
        });

        tests.put("enumConstexprGenericFunction", () -> {
            Program program = AllFunction.doPrep("""
                     enum Axis { X, Y }
                     int foo<Axis axis>() { return int(axis); }
                     int testX() { return foo<Axis.X>(); }
                     int testY() { return foo<Axis.Y>(); }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testX", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testY", new ArrayList<>(), new ArrayList<>()), 1);
            return null;
        });

        tests.put("enumConstexprGenericStruct", () -> {
            Program program = AllFunction.doPrep("""
        
            enum Axis { X, Y }
            struct Foo<Axis axis> { }
            int foo<Axis axis>(Foo<axis>) { return int(axis); }
            int testX()
            {
                Foo<Axis.X> f;
                return foo(f);
            }
            int testY()
            {
                Foo<Axis.Y> f;
                return foo(f);
            }
        
        """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testX", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "testY", new ArrayList<>(), new ArrayList<>()), 1);
            return null;
        });

        tests.put("trap", () -> {
            Program program = AllFunction.doPrep("""
                        int foo()
                        {
                           trap;
                        }
                        int foo2(int x)
                        {
                           if (x == 3)
                               trap;
                           return 4;
                        }
                        struct Bar {
                           int3 x;
                           float y;
                        }
                        Bar foo3()
                        {
                           trap;
                        }
                    """);
            AllFunction.checkFail(
                    () -> AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()),
                    (RuntimeException e) -> e instanceof WTrapError
            );
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo2", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 1)))), Constants.WSL_4);
            AllFunction.checkFail(
                    () -> AllFunction.callFunction(program, "foo2", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_3)))),
                    (RuntimeException e) -> e instanceof WTrapError
            );
            AllFunction.checkFail(
                    () -> AllFunction.callFunction(program, "foo3", new ArrayList<>(), new ArrayList<>()),
                    (RuntimeException e) -> e instanceof WTrapError
            );
            return null;
        });

        tests.put("swizzle", () -> {
            Program program = AllFunction.doPrep("""
                            
                        float foo() {
                            float4 bar = float4(3., 4., 5., 6.);
                            float3 baz = bar.zzx;
                            return baz.z;
                        }
                        float foo2() {
                            float4 bar = float4(3., 4., 5., 6.);
                            float3 baz = bar.wyz;
                            return baz.x;
                        }
                        float foo3() {
                            float3 bar = float3(3., 4., 5.);
                            float2 baz = bar.yz;
                            float4 quix = baz.yyxx;
                            return quix.z;
                        }
                            
                    """);
            AllFunction.checkFloat(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkFloat(program, AllFunction.callFunction(program, "foo2", new ArrayList<>(), new ArrayList<>()), Constants.WSL_6);
            AllFunction.checkFloat(program, AllFunction.callFunction(program, "foo3", new ArrayList<>(), new ArrayList<>()), Constants.WSL_4);
            return null;
        });

        tests.put("enumWithExplicitIntBase", () -> {
            Program program = AllFunction.doPrep("""
                     enum Foo : int {
                          War,
                          Famine,
                          Pestilence,
                          Death
                      }
                      Foo war()
                      {
                          return Foo.War;
                      }
                      Foo famine()
                      {
                          return Foo.Famine;
                      }
                      Foo pestilence()
                      {
                          return Foo.Pestilence;
                      }
                      Foo death()
                      {
                          return Foo.Death;
                      }
                      bool equals(Foo a, Foo b)
                      {
                          return a == b;
                      }
                      bool notEquals(Foo a, Foo b)
                      {
                          return a != b;
                      }
                      bool testSimpleEqual()
                      {
                          return equals(Foo.War, Foo.War);
                      }
                      bool testAnotherEqual()
                      {
                          return equals(Foo.Pestilence, Foo.Pestilence);
                      }
                      bool testNotEqual()
                      {
                          return equals(Foo.Famine, Foo.Death);
                      }
                      bool testSimpleNotEqual()
                      {
                          return notEquals(Foo.War, Foo.War);
                      }
                      bool testAnotherNotEqual()
                      {
                          return notEquals(Foo.Pestilence, Foo.Pestilence);
                      }
                      bool testNotNotEqual()
                      {
                          return notEquals(Foo.Famine, Foo.Death);
                      }
                      int intWar()
                      {
                          return int(war());
                      }
                      int intFamine()
                      {
                          return int(famine());
                      }
                      int intPestilence()
                      {
                          return int(pestilence());
                      }
                      int intDeath()
                      {
                          return int(death());
                      }
                      int warValue()
                      {
                          return war().value;
                      }
                      int famineValue()
                      {
                          return famine().value;
                      }
                      int pestilenceValue()
                      {
                          return pestilence().value;
                      }
                      int deathValue()
                      {
                          return death().value;
                      }
                      int warValueLiteral()
                      {
                          return Foo.War.value;
                      }
                      int famineValueLiteral()
                      {
                          return Foo.Famine.value;
                      }
                      int pestilenceValueLiteral()
                      {
                          return Foo.Pestilence.value;
                      }
                      int deathValueLiteral()
                      {
                          return Foo.Death.value;
                      }
                      Foo intWarBackwards()
                      {
                          return Foo(intWar());
                      }
                      Foo intFamineBackwards()
                      {
                          return Foo(intFamine());
                      }
                      Foo intPestilenceBackwards()
                      {
                          return Foo(intPestilence());
                      }
                      Foo intDeathBackwards()
                      {
                          return Foo(intDeath());
                      }
                    """);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "war", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "famine", new ArrayList<>(), new ArrayList<>()), 1);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "pestilence", new ArrayList<>(), new ArrayList<>()), Constants.WSL_2);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "death", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testSimpleEqual", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testAnotherEqual", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testNotEqual", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testSimpleNotEqual", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testAnotherNotEqual", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testNotNotEqual", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "intWar", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "intFamine", new ArrayList<>(), new ArrayList<>()), 1);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "intPestilence", new ArrayList<>(), new ArrayList<>()), Constants.WSL_2);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "intDeath", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "warValue", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "famineValue", new ArrayList<>(), new ArrayList<>()), 1);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "pestilenceValue", new ArrayList<>(), new ArrayList<>()), Constants.WSL_2);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "deathValue", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "warValueLiteral", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "famineValueLiteral", new ArrayList<>(), new ArrayList<>()), 1);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "pestilenceValueLiteral", new ArrayList<>(), new ArrayList<>()), Constants.WSL_2);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "deathValueLiteral", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "intWarBackwards", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "intFamineBackwards", new ArrayList<>(), new ArrayList<>()), 1);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "intPestilenceBackwards", new ArrayList<>(), new ArrayList<>()), Constants.WSL_2);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "intDeathBackwards", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            return null;
        });

        tests.put("enumWithUintBase", () -> {
            Program program = AllFunction.doPrep("""
                     enum Foo : uint {
                          War,
                          Famine,
                          Pestilence,
                          Death
                      }
                      Foo war()
                      {
                          return Foo.War;
                      }
                      Foo famine()
                      {
                          return Foo.Famine;
                      }
                      Foo pestilence()
                      {
                          return Foo.Pestilence;
                      }
                      Foo death()
                      {
                          return Foo.Death;
                      }
                      bool equals(Foo a, Foo b)
                      {
                          return a == b;
                      }
                      bool notEquals(Foo a, Foo b)
                      {
                          return a != b;
                      }
                      bool testSimpleEqual()
                      {
                          return equals(Foo.War, Foo.War);
                      }
                      bool testAnotherEqual()
                      {
                          return equals(Foo.Pestilence, Foo.Pestilence);
                      }
                      bool testNotEqual()
                      {
                          return equals(Foo.Famine, Foo.Death);
                      }
                      bool testSimpleNotEqual()
                      {
                          return notEquals(Foo.War, Foo.War);
                      }
                      bool testAnotherNotEqual()
                      {
                          return notEquals(Foo.Pestilence, Foo.Pestilence);
                      }
                      bool testNotNotEqual()
                      {
                          return notEquals(Foo.Famine, Foo.Death);
                      }
                      uint uintWar()
                      {
                          return uint(war());
                      }
                      uint uintFamine()
                      {
                          return uint(famine());
                      }
                      uint uintPestilence()
                      {
                          return uint(pestilence());
                      }
                      uint uintDeath()
                      {
                          return uint(death());
                      }
                      uint warValue()
                      {
                          return war().value;
                      }
                      uint famineValue()
                      {
                          return famine().value;
                      }
                      uint pestilenceValue()
                      {
                          return pestilence().value;
                      }
                      uint deathValue()
                      {
                          return death().value;
                      }
                      uint warValueLiteral()
                      {
                          return Foo.War.value;
                      }
                      uint famineValueLiteral()
                      {
                          return Foo.Famine.value;
                      }
                      uint pestilenceValueLiteral()
                      {
                          return Foo.Pestilence.value;
                      }
                      uint deathValueLiteral()
                      {
                          return Foo.Death.value;
                      }
                      Foo uintWarBackwards()
                      {
                          return Foo(uintWar());
                      }
                      Foo uintFamineBackwards()
                      {
                          return Foo(uintFamine());
                      }
                      Foo uintPestilenceBackwards()
                      {
                          return Foo(uintPestilence());
                      }
                      Foo uintDeathBackwards()
                      {
                          return Foo(uintDeath());
                      }
                    """);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "war", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "famine", new ArrayList<>(), new ArrayList<>()), 1);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "pestilence", new ArrayList<>(), new ArrayList<>()), Constants.WSL_2);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "death", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testSimpleEqual", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testAnotherEqual", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testNotEqual", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testSimpleNotEqual", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testAnotherNotEqual", new ArrayList<>(), new ArrayList<>()), false);
            AllFunction.checkBool(program, AllFunction.callFunction(program, "testNotNotEqual", new ArrayList<>(), new ArrayList<>()), true);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "uintWar", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "uintFamine", new ArrayList<>(), new ArrayList<>()), 1);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "uintPestilence", new ArrayList<>(), new ArrayList<>()), Constants.WSL_2);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "uintDeath", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "warValue", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "famineValue", new ArrayList<>(), new ArrayList<>()), 1);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "pestilenceValue", new ArrayList<>(), new ArrayList<>()), Constants.WSL_2);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "deathValue", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "warValueLiteral", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "famineValueLiteral", new ArrayList<>(), new ArrayList<>()), 1);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "pestilenceValueLiteral", new ArrayList<>(), new ArrayList<>()), Constants.WSL_2);
            AllFunction.checkUint(program, AllFunction.callFunction(program, "deathValueLiteral", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "uintWarBackwards", new ArrayList<>(), new ArrayList<>()), 0);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "uintFamineBackwards", new ArrayList<>(), new ArrayList<>()), 1);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "uintPestilenceBackwards", new ArrayList<>(), new ArrayList<>()), Constants.WSL_2);
            AllFunction.checkEnum(program, AllFunction.callFunction(program, "uintDeathBackwards", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            return null;
        });

        tests.put("enumFloatBase", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    enum Foo : float {
                                           Bar
                                       }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("enumPtrBase", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                                
                                        enum Foo : thread int* {
                                            Bar
                                        }
                                                
                                    """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("enumArrayRefBase", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    enum Foo : thread int[] {
                                         Bar
                                     }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("emptyStruct", () -> {
            Program program = AllFunction.doPrep("""
                    struct Thingy { }
                    int foo()
                    {
                        Thingy thingy;
                        return 46;
                    }
                     """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_46);
            return null;
        });

        tests.put("enumStructBase", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                     struct Thingy { }
                                     enum Foo : Thingy {
                                         Bar
                                     }
                                    """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("enumNoMembers", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                     enum Foo { }
                                    """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("simpleSwitch", () -> {
            Program program = AllFunction.doPrep("""
        
            int foo(int x)
            {
                switch (x) {
                case 767:
                    return 27;
                case 69:
                    return 7624;
                default:
                    return 49;
                }
            }
        
        """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_767)))), Constants.WSL_27);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_69)))), Constants.WSL_7624);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 0)))), Constants.WSL_49);
            return null;
        });

        tests.put("exhaustiveUint8Switch", () -> {
            String text = "double foo(uint8 x) { switch (uint8(x)) {";
            for (int i = 0; i <= Constants.WSL_0XFF; ++i) {
                text += "case " + i + ": return " + i * Constants.WSLDECIMAL_1 + ";";
            }
            text += "} }";
            Program program = AllFunction.doPrep(text);
            for (int i = 0; i < Constants.WSL_0XFF; ++i) {
                AllFunction.checkDouble(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, i)))), i * Constants.WSLDECIMAL_1);
            }
            return null;
        });

        tests.put("notQuiteExhaustiveUint8Switch", () -> {
            String text = "double foo(uint8 x) { switch (uint8(x)) {";
            for (int i = 0; i <= Constants.WSL_0XFE; ++i) {
                text += "case " + i + ": return " + i * Constants.WSLDECIMAL_1 + ";";
            }
            text += "} }";
            String text1 = text;
            AllFunction.checkFail(
                    () -> AllFunction.doPrep(text1),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("notQuiteExhaustiveUint8SwitchWithDefault", () -> {
            String text = "double foo(uint8 x) { switch (uint8(x)) {";
            for (int i = 0; i <= Constants.WSL_0XFE; ++i) {
                text += "case " + i + ": return " + i * Constants.WSLDECIMAL_1 + ";";
            }
            text += "default: return " + Constants.WSL_0XFF * Constants.WSLDECIMAL_1 + ";";
            text += "} }";
            Program program = AllFunction.doPrep(text.toString());

            for (int i = 0; i < Constants.WSL_0XFF; ++i) {
                AllFunction.checkDouble(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeUint8(program, i)))), i * Constants.WSLDECIMAL_1);
            }
            return null;
        });

        tests.put("switchFallThrough", () -> {
            // FIXME: This might become an error in future versions.
            // https://bugs.webkit.org/show_bug.cgi?id=177172
            Program program = AllFunction.doPrep("""
                            
                        int foo(int x)
                        {
                            int result = 0;
                            switch (x) {
                            case 767:
                                result += 27;
                            case 69:
                                result += 7624;
                            default:
                                result += 49;
                            }
                            return result;
                        }
                            
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_767)))), Constants.WSL_27 + Constants.WSL_7624 + Constants.WSL_49);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_69)))), Constants.WSL_7624 + Constants.WSL_49);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 0)))), Constants.WSL_49);
            return null;
        });

        tests.put("switchBreak", () -> {
            Program program = AllFunction.doPrep("""
                      int foo(int x)
                      {
                          int result = 0;
                          switch (x) {
                          case 767:
                              result += 27;
                              break;
                          case 69:
                              result += 7624;
                              break;
                          default:
                              result += 49;
                              break;
                          }
                          return result;
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_767)))), Constants.WSL_27);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, Constants.WSL_69)))), Constants.WSL_7624);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(),  new ArrayList<>(Arrays.asList(AllFunction.makeInt(program, 0)))), Constants.WSL_49);
            return null;
        });

        tests.put("enumSwitchBreakExhaustive", () -> {
            Program program = AllFunction.doPrep("""
                      enum Foo {
                               A, B, C
                           }
                           int foo(Foo x)
                           {
                               int result = 0;
                               switch (x) {
                               case Foo.A:
                                   result += 27;
                                   break;
                               case Foo.B:
                                   result += 7624;
                                   break;
                               case Foo.C:
                                   result += 49;
                                   break;
                               }
                               return result;
                           }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeEnum(program, "Foo", "A")))), Constants.WSL_27);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeEnum(program, "Foo", "B")))), Constants.WSL_7624);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeEnum(program, "Foo", "C")))), Constants.WSL_49);
            return null;
        });

        tests.put("enumSwitchBreakNotQuiteExhaustive", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    enum Foo {
                                        A, B, C, D
                                    }
                                    int foo(Foo x)
                                    {
                                        int result = 0;
                                        switch (x) {
                                        case Foo.A:
                                            result += 27;
                                            break;
                                        case Foo.B:
                                            result += 7624;
                                            break;
                                        case Foo.C:
                                            result += 49;
                                            break;
                                        }
                                        return result;
                                    }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("enumSwitchBreakNotQuiteExhaustiveWithDefault", () -> {
            Program program = AllFunction.doPrep("""
                            
                        enum Foo {
                            A, B, C
                        }
                        int foo(Foo x)
                        {
                            int result = 0;
                            switch (x) {
                            case Foo.A:
                                result += 27;
                                break;
                            case Foo.B:
                                result += 7624;
                                break;
                            default:
                                result += 49;
                                break;
                            }
                            return result;
                        }
                            
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeEnum(program, "Foo", "A")))), Constants.WSL_27);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeEnum(program, "Foo", "B")))), Constants.WSL_7624);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>(Arrays.asList(AllFunction.makeEnum(program, "Foo", "C")))), Constants.WSL_49);
            return null;
        });

        tests.put("simpleRecursiveStruct", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo {
                                              Foo foo;
                                          }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("mutuallyRecursiveStruct", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo {
                                         Bar bar;
                                     }
                                     struct Bar {
                                         Foo foo;
                                     }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("mutuallyRecursiveStructWithPointersBroken", () -> {
            Program program = AllFunction.doPrep("""
                              struct Foo {
                              thread Bar* bar;
                      int foo;
                        }
                      struct Bar {
                          thread Foo* foo;
                          int bar;
                      }
                      int foo()
                      {
                          Foo foo;
                          Bar bar;
                          foo.foo = 564;
                          bar.bar = 53;
                          return foo.bar->bar - bar.foo->foo;
                      }
                    """);
            AllFunction.checkFail(
                    () -> {
                        AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSLNEGATIVE_511);
                        return null;
                    },
                    e -> e instanceof WTrapError
            );

            return null;
        });

        tests.put("mutuallyRecursiveStructWithPointers", () -> {
            Program program = AllFunction.doPrep("""
                              struct Foo {
                              thread Bar* bar;
                      int foo;
                        }
                      struct Bar {
                          thread Foo* foo;
                          int bar;
                      }
                      int foo()
                      {
                          Foo foo;
                          Bar bar;
                          foo.bar = &bar;
                          bar.foo = &foo;
                          foo.foo = 564;
                          bar.bar = 53;
                          return foo.bar->bar - bar.foo->foo;
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSLNEGATIVE_511);
            return null;
        });

        tests.put("linkedList", () -> {
            Program program = AllFunction.doPrep("""
                              struct Node {
                              thread Node* next;
                      int value;
                        }
                      int foo()
                      {
                          Node x, y, z;
                          x.next = &y;
                          y.next = &z;
                          x.value = 1;
                          y.value = 2;
                          z.value = 3;
                          return x.next->next->value;
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_3);
            return null;
        });

        tests.put("pointerToPointer", () -> {
            Program program = AllFunction.doPrep("""
                      int foo()
                      {
                          int x;
                          thread int* p = &x;
                          thread int** pp = &p;
                          int*thread*thread qq = pp;
                          int result = 0;
                          x = 42;
                            *p = 76;
                          result += x;
                            **pp = 39;
                          result += x;
                            **qq = 83;
                          result += x;
                          return result;
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_76 + Constants.WSL_39 + Constants.WSL_83);
            return null;
        });

        tests.put("arrayRefToArrayRef", () -> {
            Program program = AllFunction.doPrep("""
                      int foo()
                      {
                          int x;
                          thread int[] p = @x;
                          thread int[][] pp = @p;
                          int[]thread[]thread qq = pp;
                          int result = 0;
                          x = 42;
                          p[0] = 76;
                          result += x;
                          pp[0][0] = 39;
                          result += x;
                          qq[0][0] = 83;
                          result += x;
                          return result;
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_76 + Constants.WSL_39 + Constants.WSL_83);
            return null;
        });

        tests.put("pointerGetter", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator.foo(device int*)
                            {
                                return 543;
                            }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator.foo(thread int*)
                            {
                                return 543;
                            }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator.foo(threadgroup int*)
                            {
                                return 543;
                            }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator.foo(constant int*)
                            {
                                return 543;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("loneSetter", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator.foo=(int, int)
                            {
                                return 543;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("setterWithMismatchedType", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            double operator.foo(int)
                            {
                                return 5.43;
                            }
                            int operator.foo=(int, int)
                            {
                                return 543;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("setterWithMatchedType", () -> {
            AllFunction.doPrep("""
                      int operator.foo( int)
                      {
                          return 5;
                      }
                      int operator.foo = ( int,int)
                      {
                          return 543;
                      }
                    """);
            return null;
        });

        tests.put("operatorWithUninferrableTypeVariable", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                            struct Foo{
                            int x;
                                  }
                            Foo operator + < T > (Foo a, Foo b)
                            {
                                Foo result;
                                result.x = a.x + b.x;
                                return result;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("operatorWithoutUninferrableTypeVariable", () -> {
            Program program = AllFunction.doPrep("""
                              struct Foo {
                      int x;
                        }
                      Foo operator+(Foo a, Foo b)
                      {
                          Foo result;
                          result.x = a.x + b.x;
                          return result;
                      }
                      int foo()
                      {
                          Foo a;
                          a.x = 645;
                          Foo b;
                          b.x = -35;
                          return (a + b).x;
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_645 - Constants.WSL_35);
            return null;
        });

        tests.put("operatorCastWithUninferrableTypeVariable", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                            struct Foo {
                            int x;
                                  }
                            operator<T> Foo(int x)
                            {
                                Foo result;
                                result.x = x;
                                return result;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("operatorCastWithTypeVariableInferredFromReturnType", () -> {
            Program program = AllFunction.doPrep("""
                              struct Foo {
                      int x;
                        }
                      protocol Barable {
                          void bar(thread Barable*, int);
                      }
                      void bar(thread double* result, int value)
                      {
                            *result = double(value);
                      }
                      operator<T:Barable> T(Foo foo)
                      {
                          T result;
                          bar(&result, foo.x);
                          return result;
                      }
                      int foo()
                      {
                          Foo foo;
                          foo.x = 75;
                          double x = double(foo);
                          return int(x * 1.5);
                      }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_112);
            return null;
        });

        tests.put("incWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator++() { return 32; }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator++(int, int) { return 76; }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("decWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator--() { return 32; }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator--(int, int) { return 76; }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("incWrongTypes", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator++(double) { return 32; }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("decWrongTypes", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator--(double) { return 32; }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("plusWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator+() { return 32; }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                              int operator +( int,int,int){
                                  return 76;
                              }
                            """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("minusWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator -() {
                                return 32;
                            }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator -( int,int,int){
                                return 76;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("timesWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator*() { return 32; }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                              int operator*(int) { return 534; }
                            """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                              int operator*( int,int,int){
                                  return 76;
                              }
                            """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("divideWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator/() {
                                return 32;
                            }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator/(int) { return 534; }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator/( int,int,int){
                                return 76;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("moduloWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator%() {
                                return 32;
                            }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator%(int) { return 534; }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator%( int,int,int){
                                return 76;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("bitAndWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator&() {
                                return 32;
                            }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator&(int) { return 534; }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator&( int,int,int){
                                return 76;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("bitOrWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator|() {
                                return 32;
                            }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator|(int) { return 534; }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator|( int,int,int){
                                return 76;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("bitXorWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator^() {
                                return 32;
                            }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator^(int) { return 534; }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator^( int,int,int){
                                return 76;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("lShiftWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator<<() {
                                return 32;
                            }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator<<(int) { return 534; }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator<<( int,int,int){
                                return 76;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("rShiftWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator>>() {
                                return 32;
                            }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator>>(int) { return 534; }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator>>( int,int,int){
                                return 76;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("bitNotWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator ~() {
                                return 32;
                            }
                              """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                            int operator ~( int,int){
                                return 534;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("equalsWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                          bool operator == (){ return true; }
                            """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                  bool operator == (int) { return true; }
                            """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                    bool operator == ( int,int,int){
                                return true;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("lessThanWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                          bool operator < (){ return true; }
                            """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                  bool operator < (int) { return true; }
                            """),
                    e -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () -> AllFunction.doPrep("""
                                    bool operator < ( int,int,int){
                                return true;
                            }
                              """),
                    e -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("lessEqualWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    bool operator<=() { return true; }
                                     """),
                    (e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    bool operator<=(int) { return true; }
                                     """),
                    (e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    bool operator<=(int, int, int) { return true; }
                                    """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("greaterWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    bool operator>() { return true; }
                                    """),
                    (e) -> e instanceof WTypeError

            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    bool operator>(int) { return true; }
                                    """),
                    (e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    bool operator>(int, int, int) { return true; }
                                    """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("greaterEqualWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    bool operator>=() { return true; }
                                    """),
                    (e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    bool operator>=(int) { return true; }
                                    """),
                    (e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    bool operator>=(int, int, int) { return true; }
                                    """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("equalsWrongReturnType", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator==(int a, int b) { return a + b; }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("notEqualsOverload", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo { }
                                    bool operator!=(Foo, Foo) { return true; }
                                    """),
                    (e) -> e instanceof WSyntaxError
            );
            return null;
        });

        tests.put("lessThanWrongReturnType", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator<(int a, int b) { return a + b; }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("lessEqualWrongReturnType", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator<=(int a, int b) { return a + b; }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("greaterThanWrongReturnType", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator>(int a, int b) { return a + b; }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("greaterEqualWrongReturnType", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator>=(int a, int b) { return a + b; }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("dotOperatorWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator.foo() { return 42; }
                                      """),
                    (e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo { }
                                    int operator.foo(Foo, int) { return 42; }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("dotOperatorSetterWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo { }
                                    Foo operator.foo=() { return 42; }
                                    """),
                    (e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo { }
                                    Foo operator.foo=(Foo) { return 42; }
                                      """),
                    (e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo { }
                                    Foo operator.foo=(Foo, int, int) { return 42; }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("loneSetterPointer", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    thread int* operator.foo=(thread int* ptr, int)
                                    {
                                        return ptr;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("setterWithNoGetterOverload", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo { }
                                    struct Bar { }
                                    int operator.foo(Foo)
                                    {
                                        return 534;
                                    }
                                    Bar operator.foo=(Bar, int)
                                    {
                                        return Bar();
                                    }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("setterWithNoGetterOverloadFixed", () -> {
            AllFunction.doPrep("""
                    struct Bar { }
                    int operator.foo(Bar)
                    {
                        return 534;
                    }
                    Bar operator.foo=(Bar, int)
                    {
                        return Bar();
                    }
                    """);
            return null;
        });

        tests.put("anderWithNothingWrong", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo {
                      int x;
                    }
                    thread int* operator&.foo(thread Foo* foo)
                    {
                        return &foo->x;
                    }
                    int foo()
                    {
                        Foo x;
                        x.x = 13;
                        return x.foo;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_13);
            return null;
        });

        tests.put("anderWithWrongNumberOfArguments", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    thread int* operator&.foo()
                                    {
                                        int x;
                                        return &x;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo {
                                        int x;
                                    }
                                    thread int* operator&.foo(thread Foo* foo, int blah)
                                    {
                                        return &foo->x;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("anderDoesntReturnPointer", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo {
                                    int x;
                                          }
                                    int operator&.foo(thread Foo* foo)
                                    {
                                        return foo->x;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("anderDoesntTakeReference", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo {
                                        int x;
                                    }
                                    thread int* operator&.foo(Foo foo)
                                    {
                                        return &foo.x;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("anderWithArrayRef", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo {
                        int x;
                    }
                    thread int* operator&.foo(thread Foo[] foo)
                    {
                        return &foo[0].x;
                    }
                    int foo()
                    {
                        Foo x;
                        x.x = 13;
                        return (@x).foo;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_13);
            return null;
        });

        tests.put("pointerIndexGetter", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator[](device int*, uint)
                                    {
                                        return 543;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator[](thread int*, uint)
                                    {
                                        return 543;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator[](threadgroup int*, uint)
                                    {
                                        return 543;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator[](constant int*, uint)
                                    {
                                        return 543;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("loneIndexSetter", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator[]=(int, uint, int)
                                    {
                                        return 543;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError
            );
            return null;
        });
        tests.put("notLoneIndexSetter", () -> {
            AllFunction.doPrep("""
                      int operator[](int, uint)
                      {
                          return 65;
                      }
                      int operator[]=(int, uint, int)
                      {
                          return 543;
                      }
                    """);
            return null;
        });

        tests.put("indexSetterWithMismatchedType", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    double operator[](int, uint)
                                    {
                                        return 5.43;
                                    }
                                    int operator[]=(int, uint, int)
                                    {
                                        return 543;
                                    }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Setter and getter must agree on value type")
            );
            return null;
        });

        tests.put("indexOperatorWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator[]() { return 42; }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator[](int) { return 42; }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator[](int, int, int) { return 42; }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("indexOperatorSetterWrongArgumentLength", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator[]=() { return 42; }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator[]=(int) { return 42; }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator[]=(int, int) { return 42; }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    int operator[]=(int, int, int, int) { return 42; }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("loneIndexSetterPointer", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    thread int* operator[]=(thread int* ptr, uint, int)
                                    {
                                        return ptr;
                                    }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("indexSetterWithNoGetterOverload", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo { }
                                    struct Bar { }
                                    int operator[](Foo, uint)
                                    {
                                        return 534;
                                    }
                                    Bar operator[]=(Bar, uint, int)
                                    {
                                        return Bar();
                                    }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("indexSetterWithNoGetterOverloadFixed", () -> {
            AllFunction.doPrep("""
                    struct Bar { }
                    int operator[](Bar, uint)
                    {
                        return 534;
                    }
                    Bar operator[]=(Bar, uint, int)
                    {
                        return Bar();
                    }
                      """);
            return null;
        });

        tests.put("indexAnderWithNothingWrong", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo {
                      int x;
                    }
                    thread int* operator&[](thread Foo* foo, uint)
                    {
                        return &foo->x;
                    }
                    int foo()
                    {
                        Foo x;
                        x.x = 13;
                        return x[666];
                    }
                      """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_13);
            return null;
        });

        tests.put("indexAnderWithWrongNumberOfArguments", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    thread int* operator&[]()
                                    {
                                        int x;
                                        return &x;
                                    }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo {
                                        int x;
                                    }
                                    thread int* operator&[](thread Foo* foo)
                                    {
                                        return &foo->x;
                                    }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo {
                                        int x;
                                    }
                                    thread int* operator&[](thread Foo* foo, uint, uint)
                                    {
                                        return &foo->x;
                                    }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError
            );
            return null;
        });

        tests.put("indexAnderDoesntReturnPointer", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo {
                                        int x;
                                    }
                                    int operator&[](thread Foo* foo, uint)
                                    {
                                        return foo->x;
                                    }
                                      """),
                    (RuntimeException e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Return type of ander is not a pointer")
            );
            return null;
        });
        tests.put("indexAnderDoesntTakeReference", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    struct Foo {
                                        int x;
                                    }
                                    thread int* operator&[](Foo foo, uint)
                                    {
                                        return &foo.x;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Parameter to ander is not a reference")
            );
            return null;
        });

        tests.put("indexAnderWithArrayRef", () -> {
            Program program = AllFunction.doPrep("""
                    struct Foo {
                      int x;
                    }
                    thread int* operator&[](thread Foo[] array, double index)
                    {
                        return &array[uint(index + 1)].x;
                    }
                    int foo()
                    {
                        Foo x;
                        x.x = 13;
                        return (@x)[double(-1)];
                    }
                     """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_13);
            return null;
        });

        tests.put("devicePtrPtr", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    void foo()
                                    {
                                        device int** p;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Illegal pointer to non-primitive type: int32* device* device")
            );
            return null;
        });

        tests.put("threadgroupPtrPtr", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    void foo()
                                    {
                                        threadgroup int** p;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Illegal pointer to non-primitive type: int32* threadgroup* threadgroup")
            );
            return null;
        });

        tests.put("constantPtrPtr", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    void foo()
                                    {
                                        constant int** p;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Illegal pointer to non-primitive type: int32* constant* constant")
            );
            return null;
        });

        tests.put("pointerIndexGetterInProtocol", () -> {
            for (String addressSpace : Constants.addressSpaces) {
                AllFunction.checkFail(
                        () ->
                                AllFunction.doPrep("""
                                        protocol Foo {
                                            int operator[](""" + addressSpace + """
                                         Foo*, uint);
                                        }
                                        struct Bar { }
                                        int operator[](Bar, uint) { return 42; }
                                          """),
                        (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Cannot have getter for pointer type")
                );
            }
            return null;
        });

        tests.put("loneIndexSetterInProtocol", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    protocol Foo {
                                        Foo operator[]=(Foo, uint, int);
                                    }
                                    struct Bar { }
                                    int operator[](Bar, uint) { return 42; }
                                    Bar operator[]=(Bar, uint, int) { return Bar(); }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Every setter must have a matching getter")
            );
            return null;
        });

        tests.put("notLoneIndexSetterInProtocol", () -> {
            AllFunction.doPrep("""
                    protocol Foo {
                        int operator[](Foo, uint);
                        Foo operator[]=(Foo, uint, int);
                    }
                    struct Bar { }
                    int operator[](Bar, uint) { return 42; }
                    Bar operator[]=(Bar, uint, int) { return Bar(); }
                      """);
            return null;
        });

        tests.put("indexSetterWithMismatchedTypeInProtocol", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    protocol Foo {
                                        double operator[](Foo, uint);
                                        Foo operator[]=(Foo, uint, int);
                                    }
                                    struct Bar { }
                                    int operator[](Bar, uint) { return 42; }
                                    Bar operator[]=(Bar, uint, int) { return Bar(); }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Setter and getter must agree on value type")
            );
            return null;
        });

        tests.put("indexOperatorWrongArgumentLengthInProtocol", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    protocol Foo {
                                        int operator[]();
                                    }
                                    struct Bar { }
                                    int operator[](Bar, uint) { return 42; }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Protocol`s type variable (Foo) not mentioned in signature")
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    protocol Foo {
                                        int operator[](Foo);
                                    }
                                    struct Bar { }
                                    int operator[](Bar, uint) { return 42; }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Incorrect number of parameters")
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    protocol Foo {
                                        int operator[](Foo, int, int);
                                    }
                                    struct Bar { }
                                    int operator[](Bar, uint) { return 42; }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Incorrect number of parameters")
            );
            return null;
        });


        tests.put("indexOperatorSetterWrongArgumentLengthInProtocol", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    protocol Foo {
                                        int operator[]=();
                                    }
                                    struct Bar { }
                                    int operator[](Bar, uint) { return 42; }
                                    Bar operator[]=(Bar, uint, int) { return Bar(); }
                                      """),
                    (e) -> e instanceof WTypeError &&  ((WTypeError)e).message.contains("Protocol`s type variable (Foo) not mentioned in signature")
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    protocol Foo {
                                        int operator[]=(Foo);
                                    }
                                    struct Bar { }
                                    int operator[](Bar, uint) { return 42; }
                                    Bar operator[]=(Bar, uint, int) { return Bar(); }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Incorrect number of parameters")
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    protocol Foo {
                                        int operator[]=(Foo, int);
                                    }
                                    struct Bar { }
                                    int operator[](Bar, uint) { return 42; }
                                    Bar operator[]=(Bar, uint, int) { return Bar(); }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Incorrect number of parameters")
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    protocol Foo {
                                        int operator[]=(Foo, int, int, int);
                                    }
                                    struct Bar { }
                                    int operator[](Bar, uint) { return 42; }
                                    Bar operator[]=(Bar, uint, int) { return Bar(); }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Incorrect number of parameters")
            );
            return null;
        });

        tests.put("loneIndexSetterPointerInProtocol", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    protocol Foo {
                                        thread int* operator[]=(thread Foo* ptr, uint, int);
                                    }
                                    struct Bar { }
                                    int operator[](Bar, uint) { return 42; }
                                    Bar operator[]=(Bar, uint, int) { return Bar(); }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Cannot have setter for pointer type")
            );
            return null;
        });

        tests.put("indexSetterWithNoGetterOverloadInProtocol", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                                
                                        protocol Foo {
                                            int operator[](int, Foo);
                                            Foo operator[]=(Foo, uint, int);
                                        }
                                        struct Bar { }
                                        int operator[](Bar, uint) { return 42; }
                                        Bar operator[]=(Bar, uint, int) { return Bar(); }
                                                
                                    """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Did not find function named operator[]= with arguments Foo,uint32")
            );
            return null;
        });

        tests.put("indexSetterWithNoGetterOverloadFixedInProtocol", () -> {
            AllFunction.doPrep("""
        
            protocol Foo {
                int operator[](Foo, uint);
                Foo operator[]=(Foo, uint, int);
            }
            struct Bar { }
            int operator[](Bar, uint) { return 42; }
            Bar operator[]=(Bar, uint, int) { return Bar(); }
        
        """);
            return null;
        });

        tests.put("indexAnderWithNothingWrongInProtocol", () -> {
            Program program = AllFunction.doPrep("""
                    protocol Foo {
                        thread int* operator&[](thread Foo* foo, uint);
                    }
                    int bar<T:Foo>(T x)
                    {
                        return x[42];
                    }
                    struct Bar { }
                    thread int* operator&[](thread Bar*, uint)
                    {
                        int result = 1234;
                        return &result;
                    }
                    int foo()
                    {
                        return bar(Bar());
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_1234);
            return null;
        });

        tests.put("indexAnderWithWrongNumberOfArgumentsInProtocol", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    protocol Foo {
                                        thread int* operator&[]();
                                    }
                                    struct Bar { }
                                    thread int* operator&[](thread Bar*, uint)
                                    {
                                        int result = 1234;
                                        return &result;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Protocol`s type variable (Foo) not mentioned in signature")
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    protocol Foo {
                                        thread int* operator&[](thread Foo* foo);
                                    }
                                    struct Bar { }
                                    thread int* operator&[](thread Bar*, uint)
                                    {
                                        int result = 1234;
                                        return &result;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Incorrect number of parameters for operator&[]")
            );

            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    protocol Foo {
                                        thread int* operator&[](thread Foo* foo, uint, uint);
                                    }
                                    struct Bar { }
                                    thread int* operator&[](thread Bar*, uint)
                                    {
                                        int result = 1234;
                                        return &result;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Incorrect number of parameters for operator&[]")
            );
            return null;
        });

        tests.put("indexAnderDoesntReturnPointerInProtocol", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    protocol Foo {
                                        int operator&[](thread Foo* foo, uint);
                                    }
                                    struct Bar { }
                                    thread int* operator&[](thread Bar*, uint)
                                    {
                                        int result = 1234;
                                        return &result;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Return type of ander is not a pointer")
            );
            return null;
        });

        tests.put("indexAnderDoesntTakeReferenceInProtocol", () -> {
            AllFunction.checkFail(
                    () ->
                            AllFunction.doPrep("""
                                    protocol Foo {
                                        thread int* operator&[](Foo foo, uint);
                                    }
                                    struct Bar { }
                                    thread int* operator&[](thread Bar*, uint)
                                    {
                                        int result = 1234;
                                        return &result;
                                    }
                                      """),
                    (e) -> e instanceof WTypeError && ((WTypeError) e).message.contains("Parameter to ander is not a reference")
            );
            return null;
        });

        tests.put("indexAnderWithArrayRefInProtocol", () -> {
            Program program = AllFunction.doPrep("""
                    protocol Foo {
                        thread int* operator&[](thread Foo[] array, double index);
                    }
                    int bar<T:Foo>(thread T[] x)
                    {
                        return x[1.5];
                    }
                    struct Bar { }
                    thread int* operator&[](thread Bar[], double)
                    {
                        int result = 1234;
                        return &result;
                    }
                    int foo()
                    {
                        Bar x;
                        return bar(@x);
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_1234);
            return null;
        });

        tests.put("andReturnedArrayRef", () -> {
            Program program = AllFunction.doPrep("""
                    thread int[] getArray()
                    {
                        int[10] x;
                        x[5] = 354;
                        return @x;
                    }
                    int foo()
                    {
                        thread int* ptr = &getArray()[5];
                        return *ptr;
                    }
                    """);
            AllFunction.checkInt(program, AllFunction.callFunction(program, "foo", new ArrayList<>(), new ArrayList<>()), Constants.WSL_354);
            return null;
        });


        return tests;
    }

    public boolean okToTest = false;

    public void doTest() {
        Map<String, Supplier<Void>> tests = getTests();
        okToTest = true;
        if (!okToTest) {
            throw new Error("Test setup is incomplete.");
        }
        ArrayList<String> names = new ArrayList<>();
        for (String s : tests.keySet()) {
            names.add(s);
        }
        Collections.sort(names);
        for (String name : names) {
            
            try {
                tests.get(name).get();
            } catch (RuntimeException e) {}
        }
        
    }
}

class Benchmark {

    public static void main(String[] args) {
        Benchmark benchmark = new Benchmark();
        benchmark.run();
    }

    public void run() {
        long beginTime = System.nanoTime();
        new WSL().run();

        long endTime = System.nanoTime();
        System.out.println("wsl: ms = " + (endTime - beginTime)/(1000.0 * 1000.0));
    }
}

class WSL {
    public void run() {
        new GetTest().doTest();
    }
}




